Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/desktop/src-tauri/src/editor_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fn strip_frame_padding(frame: RenderedFrame) -> Result<(Vec<u8>, u32), &'static
.width
.checked_mul(4)
.ok_or("overflow computing expected_stride")?;

if frame.padded_bytes_per_row == expected_stride {
Ok((frame.data, expected_stride))
} else {
Expand All @@ -30,6 +31,7 @@ fn strip_frame_padding(frame: RenderedFrame) -> Result<(Vec<u8>, u32), &'static
let end = start + expected_stride as usize;
stripped.extend_from_slice(&frame.data[start..end]);
}

Ok((stripped, expected_stride))
}
}
Expand Down
33 changes: 31 additions & 2 deletions apps/desktop/src-tauri/src/gpu_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,49 @@ pub struct SharedGpuContext {
pub queue: Arc<wgpu::Queue>,
pub adapter: Arc<wgpu::Adapter>,
pub instance: Arc<wgpu::Instance>,
pub is_software_adapter: bool,
}

static GPU: OnceCell<Option<SharedGpuContext>> = OnceCell::const_new();

pub async fn get_shared_gpu() -> Option<&'static SharedGpuContext> {
GPU.get_or_init(|| async {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
let adapter = instance

let hardware_adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
})
.await
.ok()?;
.ok();

let (adapter, is_software_adapter) = if let Some(adapter) = hardware_adapter {
tracing::info!(
adapter_name = adapter.get_info().name,
adapter_backend = ?adapter.get_info().backend,
"Using hardware GPU adapter for shared context"
);
(adapter, false)
} else {
tracing::warn!("No hardware GPU adapter found, attempting software fallback for shared context");
let software_adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
force_fallback_adapter: true,
compatible_surface: None,
})
.await
.ok()?;

tracing::info!(
adapter_name = software_adapter.get_info().name,
adapter_backend = ?software_adapter.get_info().backend,
"Using software adapter for shared context (CPU rendering - performance may be reduced)"
);
(software_adapter, true)
};

let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
Expand All @@ -72,6 +100,7 @@ pub async fn get_shared_gpu() -> Option<&'static SharedGpuContext> {
queue: Arc::new(queue),
adapter: Arc::new(adapter),
instance: Arc::new(instance),
is_software_adapter,
})
})
.await
Expand Down
62 changes: 42 additions & 20 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ pub struct RecordingInfo {
enum CurrentRecordingTarget {
Window {
id: WindowId,
bounds: LogicalBounds,
bounds: Option<LogicalBounds>,
},
Screen {
id: DisplayId,
Expand Down Expand Up @@ -841,33 +841,55 @@ struct CurrentRecording {
async fn get_current_recording(
state: MutableState<'_, App>,
) -> Result<JsonValue<Option<CurrentRecording>>, ()> {
tracing::debug!("get_current_recording called");
let state = state.read().await;

let (mode, capture_target, status) = match &state.recording_state {
RecordingState::None => return Ok(JsonValue::new(&None)),
RecordingState::Pending { mode, target } => (*mode, target, RecordingStatus::Pending),
RecordingState::Active(inner) => (
inner.mode(),
inner.capture_target(),
RecordingStatus::Recording,
),
RecordingState::None => {
tracing::debug!("get_current_recording: state is None");
return Ok(JsonValue::new(&None));
}
RecordingState::Pending { mode, target } => {
tracing::debug!("get_current_recording: state is Pending");
(*mode, target, RecordingStatus::Pending)
}
RecordingState::Active(inner) => {
tracing::debug!("get_current_recording: state is Active");
(
inner.mode(),
inner.capture_target(),
RecordingStatus::Recording,
)
}
};

let target = match capture_target {
ScreenCaptureTarget::Display { id } => CurrentRecordingTarget::Screen { id: id.clone() },
ScreenCaptureTarget::Window { id } => CurrentRecordingTarget::Window {
id: id.clone(),
bounds: scap_targets::Window::from_id(id)
.ok_or(())?
.display_relative_logical_bounds()
.ok_or(())?,
},
ScreenCaptureTarget::Area { screen, bounds } => CurrentRecordingTarget::Area {
screen: screen.clone(),
bounds: *bounds,
},
ScreenCaptureTarget::Display { id } => {
tracing::debug!("get_current_recording: target is Display");
CurrentRecordingTarget::Screen { id: id.clone() }
}
ScreenCaptureTarget::Window { id } => {
let bounds =
scap_targets::Window::from_id(id).and_then(|w| w.display_relative_logical_bounds());
tracing::debug!(
"get_current_recording: target is Window, bounds={:?}",
bounds
);
CurrentRecordingTarget::Window {
id: id.clone(),
bounds,
}
}
ScreenCaptureTarget::Area { screen, bounds } => {
tracing::debug!("get_current_recording: target is Area");
CurrentRecordingTarget::Area {
screen: screen.clone(),
bounds: *bounds,
}
}
};

tracing::debug!("get_current_recording: returning Some(CurrentRecording)");
Ok(JsonValue::new(&Some(CurrentRecording {
target,
mode,
Expand Down
12 changes: 9 additions & 3 deletions apps/desktop/src-tauri/src/screenshot_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,14 @@ impl ScreenshotEditorInstances {
}
};

let (instance, adapter, device, queue) =
let (instance, adapter, device, queue, is_software_adapter) =
if let Some(shared) = gpu_context::get_shared_gpu().await {
(
shared.instance.clone(),
shared.adapter.clone(),
shared.device.clone(),
shared.queue.clone(),
shared.is_software_adapter,
)
} else {
let instance =
Expand All @@ -262,7 +263,7 @@ impl ScreenshotEditorInstances {
})
.await
.map_err(|e| e.to_string())?;
(instance, adapter, Arc::new(device), Arc::new(queue))
(instance, adapter, Arc::new(device), Arc::new(queue), false)
};

let options = cap_rendering::RenderOptions {
Expand All @@ -285,6 +286,7 @@ impl ScreenshotEditorInstances {
meta: studio_meta,
recording_meta: recording_meta.clone(),
background_textures: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
is_software_adapter,
};

let (config_tx, mut config_rx) = watch::channel(loaded_config.unwrap_or_default());
Expand All @@ -304,7 +306,11 @@ impl ScreenshotEditorInstances {

tokio::spawn(async move {
let mut frame_renderer = FrameRenderer::new(&constants);
let mut layers = RendererLayers::new(&constants.device, &constants.queue);
let mut layers = RendererLayers::new_with_options(
&constants.device,
&constants.queue,
constants.is_software_adapter,
);
let shutdown_token = render_shutdown_token;

// Initial render
Expand Down
105 changes: 83 additions & 22 deletions apps/desktop/src-tauri/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,34 +708,95 @@ impl ShowCapWindow {
let title = CapWindowId::RecordingControls.title();
let should_protect = should_protect_window(app, &title);

let window = self
.window_builder(app, "/in-progress-recording")
.maximized(false)
.resizable(false)
.fullscreen(false)
.shadow(false)
.always_on_top(true)
.transparent(true)
.visible_on_all_workspaces(true)
.content_protected(should_protect)
.inner_size(width, height)
.position(
((monitor.size().width as f64) / monitor.scale_factor() - width) / 2.0,
(monitor.size().height as f64) / monitor.scale_factor() - height - 120.0,
)
.skip_taskbar(true)
.initialization_script(format!(
"window.COUNTDOWN = {};",
countdown.unwrap_or_default()
))
.build()?;
let pos_x = ((monitor.size().width as f64) / monitor.scale_factor() - width) / 2.0;
let pos_y =
(monitor.size().height as f64) / monitor.scale_factor() - height - 120.0;

debug!(
"InProgressRecording window: monitor size={:?}, scale={}, pos=({}, {})",
monitor.size(),
monitor.scale_factor(),
pos_x,
pos_y
);

#[cfg(target_os = "macos")]
let window = {
self.window_builder(app, "/in-progress-recording")
.maximized(false)
.resizable(false)
.fullscreen(false)
.shadow(false)
.always_on_top(true)
.transparent(true)
.visible_on_all_workspaces(true)
.content_protected(should_protect)
.inner_size(width, height)
.position(pos_x, pos_y)
.skip_taskbar(true)
.initialization_script(format!(
"window.COUNTDOWN = {};",
countdown.unwrap_or_default()
))
.build()?
};

#[cfg(windows)]
let window = {
let window = self
.window_builder(app, "/in-progress-recording")
.maximized(false)
.resizable(false)
.fullscreen(false)
.shadow(false)
.always_on_top(true)
.transparent(true)
.visible_on_all_workspaces(true)
.content_protected(should_protect)
.inner_size(width, height)
.position(pos_x, pos_y)
.skip_taskbar(false)
.initialization_script(format!(
"window.COUNTDOWN = {};",
countdown.unwrap_or_default()
))
.build()?;

window
};

debug!(
"InProgressRecording window created: label={}, inner_size={:?}, outer_position={:?}",
window.label(),
window.inner_size(),
window.outer_position()
);

#[cfg(target_os = "macos")]
{
crate::platform::set_window_level(window.as_ref().window(), 1000);
}

fake_window::spawn_fake_window_listener(app.clone(), window.clone());
#[cfg(target_os = "macos")]
{
let show_result = window.show();
debug!(
"InProgressRecording window.show() result: {:?}",
show_result
);
fake_window::spawn_fake_window_listener(app.clone(), window.clone());
}

#[cfg(windows)]
{
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let show_result = window.show();
debug!(
"InProgressRecording window.show() result: {:?}",
show_result
);
let _ = window.set_focus();
}

window
}
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import titlebar from "./utils/titlebar-state";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
experimental_prefetchInRender: true,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
},
Expand Down
8 changes: 8 additions & 0 deletions apps/desktop/src/routes/editor/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -485,11 +485,19 @@ function PreviewCanvas() {
}
});

const isWindows = navigator.userAgent.includes("Windows");

const initCanvas = (canvas: HTMLCanvasElement) => {
if (canvasTransferredRef.current) return;
const controls = canvasControls();
if (!controls) return;

if (isWindows) {
controls.initDirectCanvas(canvas);
canvasTransferredRef.current = true;
return;
}

try {
const offscreen = canvas.transferControlToOffscreen();
controls.initCanvas(offscreen);
Expand Down
Loading
Loading