diff --git a/apps/desktop-gpui/src/screenshot_editor.rs b/apps/desktop-gpui/src/screenshot_editor.rs index 7e83655f2a9..117a8e10d35 100644 --- a/apps/desktop-gpui/src/screenshot_editor.rs +++ b/apps/desktop-gpui/src/screenshot_editor.rs @@ -319,7 +319,7 @@ async fn render_still( let uniforms = ProjectUniforms::new( constants, - config, + &Arc::new(config.clone()), 0, 30, resolution_base, diff --git a/apps/desktop/src-tauri/src/export.rs b/apps/desktop/src-tauri/src/export.rs index ad1d2379871..662fe679128 100644 --- a/apps/desktop/src-tauri/src/export.rs +++ b/apps/desktop/src-tauri/src/export.rs @@ -1950,6 +1950,7 @@ async fn generate_export_preview_inner( .map(|segment| segment.display.duration) .collect::>(), ); + let project_config = Arc::new(project_config); let render_constants = Arc::new( RenderVideoConstants::new( @@ -2546,6 +2547,7 @@ async fn generate_export_preview_fast_inner( }) .await .map_err(|error| format!("Failed to synchronize export preview timing: {error}"))?; + let project_config = Arc::new(project_config); let transition_mapping = project_config.timeline.as_ref().and_then(|timeline| { if timeline.transitions.is_empty() { return None; diff --git a/apps/desktop/src-tauri/src/screenshot_editor.rs b/apps/desktop/src-tauri/src/screenshot_editor.rs index 1138336b1e7..0d903fdc4ca 100644 --- a/apps/desktop/src-tauri/src/screenshot_editor.rs +++ b/apps/desktop/src-tauri/src/screenshot_editor.rs @@ -469,7 +469,7 @@ impl ScreenshotEditorInstances { ); let shutdown_token = render_shutdown_token; let mut current_update = config_rx.borrow().clone(); - let mut current_config = current_update.config.clone(); + let mut current_config = std::sync::Arc::new(current_update.config.clone()); let mut current_revision = current_update.revision; let mut first_frame_logged = false; @@ -564,7 +564,7 @@ impl ScreenshotEditorInstances { } current_update = config_rx.borrow().clone(); current_revision = current_update.revision; - current_config = current_update.config.clone(); + current_config = std::sync::Arc::new(current_update.config.clone()); } _ = shutdown_token.cancelled() => { break; @@ -989,7 +989,7 @@ pub async fn prewarm_screenshot_renderer() { Arc::new(cap_rendering::BackgroundTextureCache::default()), ); - let config = ProjectConfiguration::default(); + let config = std::sync::Arc::new(ProjectConfiguration::default()); let mut frame_renderer = FrameRenderer::new(&constants); let mut layers = RendererLayers::new_with_options( &constants.device, @@ -1637,7 +1637,7 @@ pub async fn render_screenshot_project_for_export( pub async fn render_screenshot_png(instance: &ScreenshotEditorInstance) -> Result, String> { let path = instance.path.clone(); - let config = instance.config_tx.borrow().config.clone(); + let config = std::sync::Arc::new(instance.config_tx.borrow().config.clone()); let width = instance.image_width; let height = instance.image_height; diff --git a/crates/cap-test/src/suites/performance.rs b/crates/cap-test/src/suites/performance.rs index d7ac1a74cf4..615553bf2a6 100644 --- a/crates/cap-test/src/suites/performance.rs +++ b/crates/cap-test/src/suites/performance.rs @@ -148,7 +148,7 @@ fn skipped_result( } struct FixtureContext { - project: ProjectConfiguration, + project: Arc, render_constants: Arc, segments: Vec, } @@ -642,7 +642,7 @@ async fn load_fixture(recording_path: &Path) -> Result { } Ok(FixtureContext { - project, + project: Arc::new(project), render_constants, segments, }) diff --git a/crates/editor/examples/playback-pipeline-benchmark.rs b/crates/editor/examples/playback-pipeline-benchmark.rs index 8e772d10372..fd77e746970 100644 --- a/crates/editor/examples/playback-pipeline-benchmark.rs +++ b/crates/editor/examples/playback-pipeline-benchmark.rs @@ -423,6 +423,7 @@ async fn run_full_pipeline_benchmark( recordings: &ProjectRecordingsMeta, config: PipelineBenchmarkConfig, ) -> PipelineTimings { + let project = &Arc::new(project.clone()); let PipelineBenchmarkConfig { fps, frame_count, @@ -617,6 +618,7 @@ async fn run_scrubbing_benchmark( resolution_base: XY, force_ffmpeg: bool, ) -> PipelineTimings { + let project = &Arc::new(project.clone()); let mut timings = PipelineTimings::default(); let render_constants = match RenderVideoConstants::new( diff --git a/crates/editor/src/playback.rs b/crates/editor/src/playback.rs index 4a93c6e844e..692bda6a54b 100644 --- a/crates/editor/src/playback.rs +++ b/crates/editor/src/playback.rs @@ -1090,7 +1090,7 @@ impl Playback { .sort_by_key(|p| p.frame_number); let playback_start_frame = frame_number; - let mut cached_project = self.project.borrow().clone(); + let mut cached_project = Arc::new(self.project.borrow().clone()); let build_cursor_timelines = |project: &ProjectConfiguration| -> Vec> { @@ -1387,7 +1387,7 @@ impl Playback { drop(adopted.take()); preparing_audio_released.store(true, Ordering::Release); let project = self.project.borrow().clone(); - cached_project.audio = project.audio.clone(); + Arc::make_mut(&mut cached_project).audio = project.audio.clone(); let ticket = self.audio_output.prepare_playback(PlaySpec { segments: audio_segments.clone(), music: self.music.clone(), @@ -1460,7 +1460,7 @@ impl Playback { || clip_audio_changed(&cached_project, &next_project); let cursor_changed = cursor_timeline_settings(&cached_project.cursor) != cursor_timeline_settings(&next_project.cursor); - cached_project = next_project; + cached_project = Arc::new(next_project); if adopted.is_none() && audio_changed && let Some(generation) = audio_generation @@ -1469,7 +1469,7 @@ impl Playback { PlaySpec { segments: audio_segments.clone(), music: self.music.clone(), - project: cached_project.clone(), + project: (*cached_project).clone(), duration_secs: duration, start_playhead_secs: frame_number as f64 / fps_f64, playhead_rx: audio_playhead_rx.clone(), diff --git a/crates/editor/src/preparing_preview.rs b/crates/editor/src/preparing_preview.rs index 6a66454b3b4..f13bc342481 100644 --- a/crates/editor/src/preparing_preview.rs +++ b/crates/editor/src/preparing_preview.rs @@ -519,6 +519,7 @@ async fn run_preview( segments, cursor_assets, } = input; + let project = Arc::new(project); let metadata = recording_meta .studio_meta() .expect("Validated Studio metadata") diff --git a/crates/editor/src/preparing_preview/native_tests.rs b/crates/editor/src/preparing_preview/native_tests.rs index 578745b8df8..eb62cbb134e 100644 --- a/crates/editor/src/preparing_preview/native_tests.rs +++ b/crates/editor/src/preparing_preview/native_tests.rs @@ -382,7 +382,7 @@ async fn ordinary_composed_frames( let uniforms = match timeline { Some(timeline) => ProjectUniforms::new_with_precomputed_cursor( &constants, - &fixture.project, + &Arc::new(fixture.project.clone()), frame_number, 30, XY::new(320, 240), @@ -394,7 +394,7 @@ async fn ordinary_composed_frames( ), None => ProjectUniforms::new( &constants, - &fixture.project, + &Arc::new(fixture.project.clone()), frame_number, 30, XY::new(320, 240), diff --git a/crates/editor/src/thumbnail.rs b/crates/editor/src/thumbnail.rs index ac3534278a8..2c078355fec 100644 --- a/crates/editor/src/thumbnail.rs +++ b/crates/editor/src/thumbnail.rs @@ -1,7 +1,7 @@ use crate::{EditorInstance, editor::RendererTransitionInput}; use cap_project::{ProjectConfiguration, XY}; use cap_rendering::{ProjectUniforms, RenderedFrame, ZoomTransformTimeline}; -use std::{io::Write, path::Path}; +use std::{io::Write, path::Path, sync::Arc}; const THUMBNAIL_SIZE: XY = XY { x: 640, y: 640 }; const THUMBNAIL_FPS: u32 = 30; @@ -53,6 +53,7 @@ impl EditorInstance { let Some((project, signature)) = pending else { return Ok(None); }; + let project = Arc::new(project); let (source_time, segment) = project .get_segment_time(0.0) .ok_or("No first frame in the edited timeline")?; @@ -165,7 +166,6 @@ fn save_thumbnail( #[cfg(test)] mod tests { use super::*; - use std::sync::Arc; fn project() -> (tempfile::TempDir, Vec) { let root = tempfile::tempdir().unwrap(); diff --git a/crates/export/src/preview.rs b/crates/export/src/preview.rs index d406dce7deb..c092ec0fdf4 100644 --- a/crates/export/src/preview.rs +++ b/crates/export/src/preview.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{path::PathBuf, sync::Arc}; use base64::{Engine, engine::general_purpose::STANDARD}; use cap_editor::EditorInstance; @@ -220,6 +220,7 @@ async fn render_preview_frame( segments, total_duration, } = source; + let project_config = &Arc::new(project_config.clone()); let transition_mapping = project_config.timeline.as_ref().and_then(|timeline| { if timeline.transitions.is_empty() { return None; @@ -465,8 +466,6 @@ fn encode_preview_jpeg(frame: &RenderedFrame, quality: u8) -> Result, Ex #[cfg(test)] mod tests { - use std::sync::Arc; - use super::*; fn frame(width: u32, height: u32, stride: usize, mut state: u64) -> RenderedFrame { diff --git a/crates/export/tests/real_recording_regression.rs b/crates/export/tests/real_recording_regression.rs index 54050064193..b3267eb64e9 100644 --- a/crates/export/tests/real_recording_regression.rs +++ b/crates/export/tests/real_recording_regression.rs @@ -129,7 +129,7 @@ async fn export_sequence_render_matches_editor_reference_for_real_recording() return Err("Recording is not a studio recording".into()); }; - let project_config = recording_meta.project_config(); + let project_config = Arc::new(recording_meta.project_config()); let recordings = Arc::new( ProjectRecordingsMeta::new(&recording_meta.project_path, studio_meta) .map_err(std::io::Error::other)?, diff --git a/crates/rendering/examples/background-benchmark.rs b/crates/rendering/examples/background-benchmark.rs index c14db8d2251..785eb47f117 100644 --- a/crates/rendering/examples/background-benchmark.rs +++ b/crates/rendering/examples/background-benchmark.rs @@ -176,6 +176,7 @@ async fn main() -> Result<()> { }; project.background.source = case.source.clone(); project.background.padding = 20.0; + let project = Arc::new(project); let duration = (args.frames + args.warmup) as f64 / 60.0; let mut zoom = ZoomTransformTimeline::from_project(&project, &cursor, duration, screen_size); diff --git a/crates/rendering/examples/camera-texture-benchmark.rs b/crates/rendering/examples/camera-texture-benchmark.rs index 629d1b2cfc5..47cf76524e5 100644 --- a/crates/rendering/examples/camera-texture-benchmark.rs +++ b/crates/rendering/examples/camera-texture-benchmark.rs @@ -138,6 +138,7 @@ async fn main() -> Result<()> { }))?); } let zoom = ZoomTransformTimeline::from_project(&project, &cursor, 6.0, size); + let project = std::sync::Arc::new(project); let mut renderer = FrameRenderer::new(&constants); let mut frame_ms = Vec::new(); let mut frame_memory = Vec::new(); diff --git a/crates/rendering/src/layers/animated_gradient.rs b/crates/rendering/src/layers/animated_gradient.rs index 5890f99f90c..e44a0b2df43 100644 --- a/crates/rendering/src/layers/animated_gradient.rs +++ b/crates/rendering/src/layers/animated_gradient.rs @@ -398,7 +398,7 @@ mod tests { } fn test_uniforms(constants: &RenderVideoConstants, frame: u32) -> ProjectUniforms { - let project = ProjectConfiguration::default(); + let project = std::sync::Arc::new(ProjectConfiguration::default()); let cursor = CursorEvents::default(); let frames = DecodedSegmentFrames { screen_size: constants.options.screen_size, diff --git a/crates/rendering/src/layers/camera.rs b/crates/rendering/src/layers/camera.rs index d8ef387e5fe..073b533d1b5 100644 --- a/crates/rendering/src/layers/camera.rs +++ b/crates/rendering/src/layers/camera.rs @@ -766,7 +766,7 @@ mod tests { let zoom = ZoomTransformTimeline::from_project(project, &cursor, 6.0, size); let uniforms = ProjectUniforms::new( constants, - project, + &std::sync::Arc::new(project.clone()), (frames.segment_time * 60.0).round() as u32, 60, size, diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs index dfe7030cf43..9d176947d6f 100644 --- a/crates/rendering/src/lib.rs +++ b/crates/rendering/src/lib.rs @@ -29,7 +29,7 @@ use std::sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, }; -use std::{path::PathBuf, time::Instant}; +use std::{borrow::Cow, path::PathBuf, time::Instant}; use tokio::sync::mpsc; pub mod camera3d; @@ -770,6 +770,7 @@ pub async fn render_video_to_channel( ) -> Result<(), RenderingError> { ffmpeg::init().unwrap(); + let project = &Arc::new(project.clone()); let start_time = Instant::now(); let duration = get_duration(recordings, recording_meta, meta, project); @@ -1346,6 +1347,7 @@ pub async fn render_video_to_channel_nv12( stop_after_frames_sent: Option, startup_breakdown_ms: Option>>>, ) -> Result<(), RenderingError> { + let project = &Arc::new(project.clone()); let ffmpeg_init_start = Instant::now(); ffmpeg::init().unwrap(); let ffmpeg_init_ms = ffmpeg_init_start.elapsed().as_millis() as u64; @@ -2091,7 +2093,7 @@ impl InFlightFrame { struct TransitionExportContext<'a> { constants: &'a RenderVideoConstants, - project: &'a ProjectConfiguration, + project: &'a Arc, render_segments: &'a [RenderSegment], outgoing_zoom_timelines: &'a mut [ZoomTransformTimeline], precomputed_cursor_timelines: &'a [Arc], @@ -2898,7 +2900,7 @@ pub struct ProjectUniforms { /// their edges); the cursor and click-ripple layers scale by it. pub cursor_clip_visibility: f32, pub click_ripples: Vec, - pub project: ProjectConfiguration, + pub project: Arc, pub zoom: InterpolatedZoom, pub scene: InterpolatedScene, pub split: Option, @@ -3915,7 +3917,7 @@ impl ProjectUniforms { #[allow(clippy::too_many_arguments)] pub fn new( constants: &RenderVideoConstants, - project: &ProjectConfiguration, + project: &Arc, frame_number: u32, fps: u32, resolution_base: XY, @@ -3961,7 +3963,7 @@ impl ProjectUniforms { #[allow(clippy::too_many_arguments)] pub fn new_with_precomputed_cursor( constants: &RenderVideoConstants, - project: &ProjectConfiguration, + project: &Arc, frame_number: u32, fps: u32, resolution_base: XY, @@ -4024,7 +4026,7 @@ impl ProjectUniforms { #[allow(clippy::too_many_arguments)] fn new_inner( constants: &RenderVideoConstants, - project: &ProjectConfiguration, + shared_project: &Arc, frame_number: u32, fps: u32, resolution_base: XY, @@ -4034,6 +4036,7 @@ impl ProjectUniforms { zoom_timeline: &ZoomTransformTimeline, cursor_interp_fn: &dyn Fn(f32) -> Option, ) -> Self { + let project = shared_project.as_ref(); let options = &constants.options; let output_size = Self::get_output_size(options, project, resolution_base); let fps_f32 = fps.max(1) as f32; @@ -5108,7 +5111,10 @@ impl ProjectUniforms { frame_chrome, notch, display_outer_bounds, - project: styled_project.into_owned(), + project: match styled_project { + Cow::Borrowed(_) => Arc::clone(shared_project), + Cow::Owned(styled) => Arc::new(styled), + }, zoom, scene, split: split_layout, @@ -5907,7 +5913,15 @@ mod style_image_tests { let zoom = ZoomTransformTimeline::from_project(&project, &cursor, 6.0, size); let at = |project: &ProjectConfiguration, frame| { ProjectUniforms::new( - &constants, project, frame, 60, size, &cursor, &frames, 6.0, &zoom, + &constants, + &Arc::new(project.clone()), + frame, + 60, + size, + &cursor, + &frames, + 6.0, + &zoom, ) }; let uniforms = at(&project, 180); @@ -6133,7 +6147,7 @@ mod style_image_tests { for frame_number in [41, 42, 60, 83, 84] { let uniforms = ProjectUniforms::new( &constants, - &project, + &Arc::new(project.clone()), frame_number, 60, XY::new(160, 90), @@ -6206,7 +6220,7 @@ mod style_image_tests { ); let uniforms = ProjectUniforms::new( &constants, - &project, + &Arc::new(project.clone()), 60, 60, XY::new(160, 90), @@ -6233,7 +6247,7 @@ mod style_image_tests { let visibility_at = |frame_number: u32| { ProjectUniforms::new( &constants, - &project, + &Arc::new(project.clone()), frame_number, 60, XY::new(160, 90), @@ -6606,7 +6620,7 @@ mod nv12_flush_tests { for frame_number in 0..5u32 { let uniforms = ProjectUniforms::new( &constants, - &project, + &Arc::new(project.clone()), frame_number, 60, XY::new(4, 4), @@ -6723,7 +6737,7 @@ mod nv12_flush_tests { let zoom = ZoomTransformTimeline::from_project(&project, &cursor, 1.0, XY::new(4, 4)); let uniforms = ProjectUniforms::new( &constants, - &project, + &Arc::new(project.clone()), 7, 60, XY::new(4, 4), @@ -9095,6 +9109,7 @@ mod project_uniforms_tests { Some(project.cursor.click_spring_config()), ); let zoom = ZoomTransformTimeline::from_project(&project, &events, 2.0, size); + let project = Arc::new(project); let mut legacy_renderer = FrameRenderer::new(&constants); let mut indexed_renderer = FrameRenderer::new(&constants); let mut hidden_renderer = FrameRenderer::new(&constants); @@ -9158,7 +9173,7 @@ mod project_uniforms_tests { 2.0, &zoom, ); - hidden.project.cursor.hide = true; + Arc::make_mut(&mut hidden.project).cursor.hide = true; let hidden = hidden_renderer .render_immediate(frames, hidden, &events, true, &mut hidden_layers) .await @@ -9285,7 +9300,7 @@ mod project_uniforms_tests { } let uniforms = ProjectUniforms::new( &constants, - &project, + &Arc::new(project.clone()), 0, 30, screen_size, @@ -9592,7 +9607,7 @@ mod project_uniforms_tests { ); let uniforms = ProjectUniforms::new( &constants, - &project, + &Arc::new(project.clone()), frame_number, 60, XY::new(800, 450), @@ -9621,7 +9636,7 @@ mod project_uniforms_tests { assert!((opacity - expected).abs() < 0.001); } let mut display_uniforms = uniforms.clone(); - display_uniforms.project.cursor.hide = true; + Arc::make_mut(&mut display_uniforms.project).cursor.hide = true; let display = renderer .render_immediate(frames.clone(), display_uniforms, &events, true, &mut layers) .await @@ -9633,7 +9648,7 @@ mod project_uniforms_tests { for ripple_only in [false, true] { let mut overlay_uniforms = uniforms.clone(); if ripple_only { - overlay_uniforms.project.cursor.hide = true; + Arc::make_mut(&mut overlay_uniforms.project).cursor.hide = true; overlay_uniforms.click_ripples = vec![ClickRipple { position: Coord::new(position), progress: 0.35, @@ -9657,7 +9672,7 @@ mod project_uniforms_tests { cursor_only_frames.camera_frame = None; let mut cursor_only_uniforms = ProjectUniforms::new( &constants, - &project, + &Arc::new(project.clone()), frame_number, 60, XY::new(800, 450), @@ -9666,12 +9681,14 @@ mod project_uniforms_tests { 4.0, &zoom_timeline, ); - cursor_only_uniforms.project.background.source = BackgroundSource::Color { + Arc::make_mut(&mut cursor_only_uniforms.project) + .background + .source = BackgroundSource::Color { value: [0, 0, 0], alpha: 0, }; if ripple_only { - cursor_only_uniforms.project.cursor.hide = true; + Arc::make_mut(&mut cursor_only_uniforms.project).cursor.hide = true; cursor_only_uniforms.click_ripples = vec![ClickRipple { position: Coord::new(position), progress: 0.35, @@ -9714,7 +9731,7 @@ mod project_uniforms_tests { ); report_pixel_changes("overlay vs original", &overlay, &display, marker_center); let mut hidden_uniforms = uniforms.clone(); - hidden_uniforms.project.cursor.hide = true; + Arc::make_mut(&mut hidden_uniforms.project).cursor.hide = true; let hidden_repeat = renderer .render_immediate( frames.clone(), @@ -9844,6 +9861,147 @@ mod project_uniforms_tests { assert!(delta < 0.2, "focus moved too far ahead: {delta}"); assert!(delta > -0.25, "focus lagged too far behind: {delta}"); } + + fn uniforms_at( + constants: &RenderVideoConstants, + project: &Arc, + frame_number: u32, + ) -> ProjectUniforms { + let screen_size = constants.options.screen_size; + let events = CursorEvents::default(); + let mut zoom_timeline = + ZoomTransformTimeline::from_project(project, &events, 60.0, screen_size); + zoom_timeline.ensure_precomputed_until(60.0); + let frames = DecodedSegmentFrames { + screen_size, + screen_frame: Some(DecodedFrame::new(Vec::new(), screen_size.x, screen_size.y)), + camera_frame: None, + segment_time: frame_number as f32 / 30.0, + recording_time: frame_number as f32 / 30.0, + segment_has_camera: false, + }; + ProjectUniforms::new( + constants, + project, + frame_number, + 30, + screen_size, + &events, + &frames, + 60.0, + &zoom_timeline, + ) + } + + fn captioned_project(segments: u32) -> ProjectConfiguration { + let captions: Vec<_> = (0..segments) + .map(|i| { + let start = f64::from(i) * 3.0; + serde_json::json!({ + "id": format!("caption-{i}"), + "start": start, + "end": start + 2.5, + "text": "the quick brown fox jumps over the lazy dog", + "words": (0..8).map(|w| serde_json::json!({ + "text": format!("word{w}"), + "start": start + f64::from(w) * 0.3, + "end": start + f64::from(w) * 0.3 + 0.25, + })).collect::>(), + }) + }) + .collect(); + serde_json::from_value(serde_json::json!({ + "timeline": { + "segments": [{"recordingClip": 0, "start": 0.0, "end": f64::from(segments) * 3.0, "timescale": 1.0}], + "zoomSegments": [], + "captionSegments": captions, + }, + "captions": {"segments": captions, "settings": {}}, + })) + .unwrap() + } + + #[tokio::test] + async fn uniforms_share_the_project_unless_a_style_segment_is_active() { + let constants = cursor_test_constants().await; + let mut project = captioned_project(10); + project.timeline.as_mut().unwrap().style_segments = serde_json::from_value( + serde_json::json!([{"start": 1.0, "end": 2.0, "overrides": {"background": {"padding": 30.0}}}]), + ) + .unwrap(); + let project = Arc::new(project); + + let before = uniforms_at(&constants, &project, 15); + let styled = uniforms_at(&constants, &project, 45); + let after = uniforms_at(&constants, &project, 75); + + assert!(Arc::ptr_eq(&before.project, &project)); + assert!(Arc::ptr_eq(&after.project, &project)); + assert!(!Arc::ptr_eq(&styled.project, &project)); + assert_eq!(styled.project.background.padding, 30.0); + assert_eq!( + styled.project.captions.as_ref().unwrap().segments.len(), + project.captions.as_ref().unwrap().segments.len() + ); + } + + #[tokio::test] + #[ignore] + async fn benchmark_project_uniforms() { + use std::hint::black_box; + + let constants = cursor_test_constants().await; + let screen_size = constants.options.screen_size; + let events = CursorEvents::default(); + let frames = DecodedSegmentFrames { + screen_size, + screen_frame: Some(DecodedFrame::new(Vec::new(), screen_size.x, screen_size.y)), + camera_frame: None, + segment_time: 0.0, + recording_time: 0.0, + segment_has_camera: false, + }; + for (transcript, segments) in [ + ("none", 0), + ("10 min", 200), + ("30 min", 600), + ("60 min", 1200), + ] { + let project = Arc::new(captioned_project(segments)); + let mut zoom_timeline = + ZoomTransformTimeline::from_project(&project, &events, 60.0, screen_size); + zoom_timeline.ensure_precomputed_until(60.0); + let mut runs: Vec = (0..400) + .map(|frame| { + let start = Instant::now(); + let uniforms = ProjectUniforms::new( + &constants, + &project, + frame % 1800, + 30, + screen_size, + &events, + &frames, + 60.0, + &zoom_timeline, + ); + let elapsed = start.elapsed().as_secs_f64() * 1_000_000.0; + black_box(uniforms); + elapsed + }) + .collect(); + runs.sort_by(f64::total_cmp); + println!( + "{}", + serde_json::json!({ + "transcript": transcript, + "caption_segments": segments, + "median_us": runs[runs.len() / 2], + "p95_us": runs[runs.len() * 95 / 100], + }) + ); + } + } } #[cfg(test)] diff --git a/crates/rendering/src/overlay_layers/native_tests.rs b/crates/rendering/src/overlay_layers/native_tests.rs index db1695c6b31..f137c0ae24b 100644 --- a/crates/rendering/src/overlay_layers/native_tests.rs +++ b/crates/rendering/src/overlay_layers/native_tests.rs @@ -88,7 +88,7 @@ fn uniforms( ZoomTransformTimeline::from_project(project, &cursor, 3.0, constants.options.screen_size); ProjectUniforms::new( constants, - project, + &std::sync::Arc::new(project.clone()), frame, 30, XY::new(320, 240), @@ -289,7 +289,7 @@ async fn restricted_late_uniforms_fail_before_prepare_or_direct_render_work() { let late_project = uniforms(&constants, &with_overlay("image", "must-not-open.png"), 30); let mut late_text = uniforms(&constants, &with_overlay("text", ""), 30); assert!(!late_text.texts.is_empty()); - late_text.project = project; + late_text.project = std::sync::Arc::new(project); let mut session = RenderSession::new(&constants.device, clean.output_size.0, clean.output_size.1); let mut encoder = constants.device.create_command_encoder(&Default::default()); diff --git a/crates/rendering/tests/background_resize.rs b/crates/rendering/tests/background_resize.rs index e52e297b136..28996302fb3 100644 --- a/crates/rendering/tests/background_resize.rs +++ b/crates/rendering/tests/background_resize.rs @@ -75,7 +75,7 @@ async fn image_background_resize_matches_fresh_layers() { let zoom = ZoomTransformTimeline::from_project(&project, &cursor, 1.0, screen_size); let uniforms = ProjectUniforms::new( &constants, - &project, + &std::sync::Arc::new(project.clone()), 0, 30, XY::new(128, 128),