Camera background blur and new city backgrounds#1755
Conversation
…entation pipelines Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
… on stop Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
…, and cleanup hardening Made-with: Cursor
…ew toggle Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
…in camera preview Made-with: Cursor
… size Made-with: Cursor
|
Paragon Review Skipped Hi @richiemcilroy! Your Polarity credit balance is insufficient to complete this review. Please visit https://app.paragon.run to finish your review. |
| let current = self.current_texture; | ||
| let processor_generation = processor.output_generation(); | ||
|
|
||
| let cache_hit = matches!( |
There was a problem hiding this comment.
The cache key is so specific that this will rebuild the blur bind group basically every frame (since current_texture / recording_time change). Since the bind group just points at the processor’s output view, you can usually cache on (mode, output_generation).
| let cache_hit = matches!( | |
| let cache_hit = matches!( | |
| self.blur_cache, | |
| Some(entry) if entry.mode == mode && entry.output_generation == processor_generation | |
| ) && self.blur_bind_group.is_some(); |
| } | ||
|
|
||
| pub fn run_inference(&mut self, rgba_256x256: &[u8]) -> anyhow::Result<Vec<f32>> { | ||
| let channel_size = MODEL_INPUT_SIZE * MODEL_INPUT_SIZE; |
There was a problem hiding this comment.
rgba_256x256 is assumed to be the right size; if the readback ever returns a shorter buffer this will panic on indexing. A quick length check keeps it safe.
| let channel_size = MODEL_INPUT_SIZE * MODEL_INPUT_SIZE; | |
| let channel_size = MODEL_INPUT_SIZE * MODEL_INPUT_SIZE; | |
| let expected_len = channel_size * 4; | |
| if rgba_256x256.len() < expected_len { | |
| return Err(anyhow::anyhow!( | |
| "Expected {expected_len} RGBA bytes, got {}", | |
| rgba_256x256.len() | |
| )); | |
| } | |
| let mut flat = vec![0.0f32; 3 * channel_size]; |
| } | ||
| Err(e) => { | ||
| tracing::warn!("Camera background blur: CoreML EP registration failed, using CPU: {e}"); | ||
| Session::builder().unwrap_or_else(|_| panic!("Failed to recreate session builder")) |
There was a problem hiding this comment.
The panic fallback here seems a bit harsh for something that’s essentially an optional acceleration path. Might be worth making the provider registration helpers return anyhow::Result<SessionBuilder> and just fall back to CPU (or propagate) without panicking if builder creation fails.
| return; | ||
| }; | ||
|
|
||
| let _ = processor.process(device, queue, source_texture, mode); |
There was a problem hiding this comment.
No need for let _ = here; the return value isn’t #[must_use].
| let _ = processor.process(device, queue, source_texture, mode); | |
| processor.process(device, queue, source_texture, mode); |
| size: crate::camera::CAMERA_PRESET_LARGE, | ||
| shape: CameraPreviewShape::Full, | ||
| mirrored: current_mirrored, | ||
| background_blur: cap_project::BackgroundBlurMode::Off, |
There was a problem hiding this comment.
Background blur is always reset to
Off when recording starts
background_blur is hard-coded to BackgroundBlurMode::Off in the camera state that start_recording sends to the preview. project_config_from_recording then propagates this value into the project config, so exported/rendered recordings will never have blur applied even if the user had blur enabled before clicking Record. If this is intentional (blur is a live-preview-only feature), a comment would help clarify the design decision.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/recording.rs
Line: 607-610
Comment:
**Background blur is always reset to `Off` when recording starts**
`background_blur` is hard-coded to `BackgroundBlurMode::Off` in the camera state that `start_recording` sends to the preview. `project_config_from_recording` then propagates this value into the project config, so exported/rendered recordings will never have blur applied even if the user had blur enabled before clicking Record. If this is intentional (blur is a live-preview-only feature), a comment would help clarify the design decision.
How can I resolve this? If you propose a fix, please make it concise.
Camera background blur (on-device segmentation, Light/Heavy), a cities wallpaper pack, smaller stock backgrounds, plus sleep/wake recovery and window-position persistence.
Greptile Summary
This PR introduces on-device camera background blur (Light/Heavy) using an ONNX selfie-segmentation model + GPU Gaussian blur pipeline (
cap-camera-effectscrate), a cities wallpaper pack, and several reliability improvements — sleep/wake recovery viapower_observer, cancellableFakeWindowListeners, and debounced window-position persistence.WsBlurState::process(camera_legacy.rs): the_strideparameter is suppressed andbytes_per_rowis hard-coded towidth * 4; if the FFmpeg scaler outputs a padded row stride the GPU texture upload will be corrupted.segmentation.rs): ifSession::builder()fails in the EP-registration error branch the app panics instead of propagating an error.Offon record start (recording.rs): exported recordings will never carry blur even when the user had it enabled; intent should be clarified with a comment.Confidence Score: 4/5
Safe to merge after addressing the stride mismatch in WsBlurState::process; all other issues are non-blocking.
One P1 finding (stride ignored in WsBlurState::process) that can corrupt the blurred WebSocket preview output when the FFmpeg scaler produces a padded stride. The remaining findings are P2 — a panic risk in an unlikely fallback path and an undocumented design choice around blur being disabled during recording.
apps/desktop/src-tauri/src/camera_legacy.rs (stride bug), crates/camera-effects/src/segmentation.rs (panic fallback)
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Camera Frame - FFmpeg RGBA] --> B{blur enabled?} B -- No --> C[Raw frame to WSFrame / PreparedTexture.render] B -- Yes --> D[write_texture to blur_source_texture] D --> E[BlurProcessor.process] E --> F1[Downsample 256x256] F1 --> F2[GPU readback async] F2 --> F3[ONNX selfie segmentation inference] F3 --> F4[EMA smoothed mask upload] F4 --> F5[Two-pass Gaussian blur] F5 --> F6[Composite: sharp + blurred + mask] F6 --> G{Output path} G -- Native preview --> H[copy_texture_to_texture + render_no_upload + present] G -- WS legacy preview --> I[Double-buffered GPU readback to WSFrame] G -- Rendering export --> J[attach_shared_blur + blur_bind_group in CameraLayer]Comments Outside Diff (2)
apps/desktop/src-tauri/src/camera_legacy.rs, line 581-594 (link)_strideis accepted but never used —bytes_per_rowis hardcoded towidth * 4instead. If the FFmpeg RGBA frame has row padding (stride > width × 4, which can happen when the scaler outputs an alignment-padded buffer), wgpu will misinterpret the row boundaries and the blurred output will be corrupted. The_prefix only suppresses the unused-variable warning but does not make the mismatch safe.Prompt To Fix With AI
crates/camera-effects/src/segmentation.rs, line 1107-1113 (link)When CoreML (or DirectML on Windows) EP registration fails, the error branch discards the original
builderand callsSession::builder().unwrap_or_else(|_| panic!(...)). IfSession::builder()fails in this fallback, the application panics — but that failure is independent of the CoreML/DirectML error and could happen on resource-constrained systems. Returning the error viaanyhow::Resultwould let the caller decide how to handle it, rather than crashing.The same pattern exists in
try_register_directml.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "chore(desktop): re-encode stock blue and..." | Re-trigger Greptile