fix lag spike when first displaying audio clip
This commit is contained in:
parent
b87e4325c2
commit
6bd400d353
|
|
@ -181,6 +181,10 @@ pub struct TimelinePane {
|
|||
|
||||
/// Whether to display time as seconds or measures
|
||||
time_display_format: TimeDisplayFormat,
|
||||
|
||||
/// Waveform upload progress: pool_index -> frames uploaded so far.
|
||||
/// Tracks chunked GPU uploads across frames to avoid hitches.
|
||||
waveform_upload_progress: std::collections::HashMap<usize, usize>,
|
||||
}
|
||||
|
||||
/// Check if a clip type can be dropped on a layer type
|
||||
|
|
@ -367,6 +371,7 @@ impl TimelinePane {
|
|||
layer_control_clicked: false,
|
||||
context_menu_clip: None,
|
||||
time_display_format: TimeDisplayFormat::Seconds,
|
||||
waveform_upload_progress: std::collections::HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1497,7 +1502,7 @@ impl TimelinePane {
|
|||
/// Render layer rows (timeline content area)
|
||||
/// Returns video clip hover data for processing after input handling
|
||||
fn render_layers(
|
||||
&self,
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
rect: egui::Rect,
|
||||
theme: &crate::theme::Theme,
|
||||
|
|
@ -2128,11 +2133,27 @@ impl TimelinePane {
|
|||
let screen_size = ui.ctx().content_rect().size();
|
||||
|
||||
let pending_upload = if waveform_gpu_dirty.contains(audio_pool_index) {
|
||||
waveform_gpu_dirty.remove(audio_pool_index);
|
||||
// Chunked upload: track progress across frames
|
||||
let chunk = crate::waveform_gpu::UPLOAD_CHUNK_FRAMES;
|
||||
let progress = self.waveform_upload_progress.get(audio_pool_index).copied().unwrap_or(0);
|
||||
let next_end = (progress + chunk).min(total_frames);
|
||||
let frame_limit = Some(next_end);
|
||||
|
||||
if next_end >= total_frames {
|
||||
// Final chunk — done
|
||||
waveform_gpu_dirty.remove(audio_pool_index);
|
||||
self.waveform_upload_progress.remove(audio_pool_index);
|
||||
} else {
|
||||
// More chunks needed
|
||||
self.waveform_upload_progress.insert(*audio_pool_index, next_end);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
Some(crate::waveform_gpu::PendingUpload {
|
||||
samples: samples.clone(),
|
||||
sample_rate: *sr,
|
||||
channels: *ch,
|
||||
frame_limit,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
|
@ -2239,6 +2260,7 @@ impl TimelinePane {
|
|||
samples: samples.clone(),
|
||||
sample_rate: *sr,
|
||||
channels: *ch,
|
||||
frame_limit: None, // recording uses incremental path
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -108,8 +108,16 @@ pub struct PendingUpload {
|
|||
pub samples: std::sync::Arc<Vec<f32>>,
|
||||
pub sample_rate: u32,
|
||||
pub channels: u32,
|
||||
/// If set, only upload up to this many frames (for chunked uploads).
|
||||
/// The texture is allocated at full size, but total_frames is set to
|
||||
/// the limited count so subsequent calls use the incremental path.
|
||||
pub frame_limit: Option<usize>,
|
||||
}
|
||||
|
||||
/// Maximum frames to convert and upload per frame (~250K frames ≈ 5.6s at 44.1kHz).
|
||||
/// Keeps the CPU f32→f16 conversion under ~2-3ms per frame.
|
||||
pub const UPLOAD_CHUNK_FRAMES: usize = 250_000;
|
||||
|
||||
impl WaveformGpuResources {
|
||||
pub fn new(device: &wgpu::Device, target_format: wgpu::TextureFormat) -> Self {
|
||||
// Render shader
|
||||
|
|
@ -282,18 +290,22 @@ impl WaveformGpuResources {
|
|||
samples: &[f32],
|
||||
sample_rate: u32,
|
||||
channels: u32,
|
||||
frame_limit: Option<usize>,
|
||||
) -> Vec<wgpu::CommandBuffer> {
|
||||
let new_total_frames = samples.len() / channels.max(1) as usize;
|
||||
if new_total_frames == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// For incremental path, also respect frame_limit
|
||||
let effective_frames = frame_limit.map_or(new_total_frames, |lim| lim.min(new_total_frames));
|
||||
|
||||
// If entry exists and texture is large enough, do an incremental update
|
||||
let incremental = if let Some(entry) = self.entries.get(&pool_index) {
|
||||
let new_tex_height = (new_total_frames as u32 + TEX_WIDTH - 1) / TEX_WIDTH;
|
||||
if new_tex_height <= entry.tex_height && new_total_frames > entry.total_frames as usize {
|
||||
if new_tex_height <= entry.tex_height && effective_frames > entry.total_frames as usize {
|
||||
Some((entry.total_frames as usize, entry.tex_height))
|
||||
} else if new_total_frames <= entry.total_frames as usize {
|
||||
} else if effective_frames <= entry.total_frames as usize {
|
||||
return Vec::new(); // No new data
|
||||
} else {
|
||||
None // Texture too small, need full recreate
|
||||
|
|
@ -305,7 +317,7 @@ impl WaveformGpuResources {
|
|||
if let Some((old_frames, tex_height)) = incremental {
|
||||
// Write only the NEW rows into the existing texture
|
||||
let start_row = old_frames as u32 / TEX_WIDTH;
|
||||
let end_row = (new_total_frames as u32 + TEX_WIDTH - 1) / TEX_WIDTH;
|
||||
let end_row = (effective_frames as u32 + TEX_WIDTH - 1) / TEX_WIDTH;
|
||||
let rows_to_write = end_row - start_row;
|
||||
|
||||
let row_texel_count = (TEX_WIDTH * rows_to_write) as usize;
|
||||
|
|
@ -314,7 +326,7 @@ impl WaveformGpuResources {
|
|||
let row_start_frame = start_row as usize * TEX_WIDTH as usize;
|
||||
for frame in 0..(rows_to_write as usize * TEX_WIDTH as usize) {
|
||||
let global_frame = row_start_frame + frame;
|
||||
if global_frame >= new_total_frames {
|
||||
if global_frame >= effective_frames {
|
||||
break;
|
||||
}
|
||||
let sample_offset = global_frame * channels as usize;
|
||||
|
|
@ -364,11 +376,11 @@ impl WaveformGpuResources {
|
|||
TEX_WIDTH,
|
||||
tex_height,
|
||||
mip_count,
|
||||
new_total_frames as u32,
|
||||
effective_frames as u32,
|
||||
);
|
||||
|
||||
// Update total_frames after borrow of entry is done
|
||||
self.entries.get_mut(&pool_index).unwrap().total_frames = new_total_frames as u64;
|
||||
self.entries.get_mut(&pool_index).unwrap().total_frames = effective_frames as u64;
|
||||
return cmds;
|
||||
}
|
||||
|
||||
|
|
@ -378,12 +390,15 @@ impl WaveformGpuResources {
|
|||
self.per_instance.retain(|&(pi, _), _| pi != pool_index);
|
||||
|
||||
let total_frames = new_total_frames;
|
||||
// Upload only effective_frames worth of data on this call
|
||||
let upload_frames = effective_frames;
|
||||
|
||||
// For live recording (pool_index == usize::MAX), pre-allocate extra texture
|
||||
// height to avoid frequent full recreates as recording grows.
|
||||
// Allocate 60 seconds ahead so incremental updates can fill without recreating.
|
||||
// When chunking, always allocate for the full total so incremental updates fit.
|
||||
let alloc_frames = if pool_index == usize::MAX {
|
||||
let extra = sample_rate as usize * 60; // 60s of mono frames (texture is per-frame, not per-sample)
|
||||
let extra = sample_rate as usize * 60;
|
||||
total_frames + extra
|
||||
} else {
|
||||
total_frames
|
||||
|
|
@ -411,12 +426,16 @@ impl WaveformGpuResources {
|
|||
let seg_end_frame = ((seg + 1) as u64 * frames_per_segment as u64)
|
||||
.min(total_frames as u64);
|
||||
let seg_frame_count = (seg_end_frame - seg_start_frame) as u32;
|
||||
// Limit actual data processing to upload_frames (for chunked uploads)
|
||||
let seg_upload_end = ((seg + 1) as u64 * frames_per_segment as u64)
|
||||
.min(upload_frames as u64);
|
||||
let seg_upload_count = seg_upload_end.saturating_sub(seg_start_frame) as u32;
|
||||
|
||||
// Allocate texture large enough for future growth (recording) or exact fit (normal)
|
||||
// Allocate texture large enough for the FULL data (not just this chunk)
|
||||
let alloc_seg_frames = if pool_index == usize::MAX {
|
||||
(alloc_frames as u32).min(seg_frame_count + sample_rate * 60)
|
||||
} else {
|
||||
seg_frame_count
|
||||
seg_frame_count // full size so incremental updates fit
|
||||
};
|
||||
let tex_height = (alloc_seg_frames + TEX_WIDTH - 1) / TEX_WIDTH;
|
||||
let mip_count = compute_mip_count(TEX_WIDTH, tex_height);
|
||||
|
|
@ -440,12 +459,12 @@ impl WaveformGpuResources {
|
|||
});
|
||||
|
||||
// Pack raw samples into Rgba16Float data for mip 0
|
||||
// Only pack rows containing actual data (not the pre-allocated empty region)
|
||||
let data_height = (seg_frame_count + TEX_WIDTH - 1) / TEX_WIDTH;
|
||||
// Only pack rows containing data uploaded this chunk
|
||||
let data_height = (seg_upload_count + TEX_WIDTH - 1) / TEX_WIDTH;
|
||||
let data_texel_count = (TEX_WIDTH * data_height) as usize;
|
||||
let mut mip0_data: Vec<half::f16> = vec![half::f16::ZERO; data_texel_count * 4];
|
||||
|
||||
for frame in 0..seg_frame_count as usize {
|
||||
for frame in 0..seg_upload_count as usize {
|
||||
let global_frame = seg_start_frame as usize + frame;
|
||||
let sample_offset = global_frame * channels as usize;
|
||||
|
||||
|
|
@ -490,14 +509,14 @@ impl WaveformGpuResources {
|
|||
);
|
||||
}
|
||||
|
||||
// Generate mipmaps via compute shader
|
||||
// Generate mipmaps via compute shader (only for uploaded data)
|
||||
let cmds = self.generate_mipmaps(
|
||||
device,
|
||||
&texture,
|
||||
TEX_WIDTH,
|
||||
tex_height,
|
||||
mip_count,
|
||||
seg_frame_count,
|
||||
seg_upload_count,
|
||||
);
|
||||
all_command_buffers.extend(cmds);
|
||||
|
||||
|
|
@ -549,7 +568,7 @@ impl WaveformGpuResources {
|
|||
render_bind_groups,
|
||||
uniform_buffers,
|
||||
frames_per_segment,
|
||||
total_frames: total_frames as u64,
|
||||
total_frames: upload_frames as u64, // only what was uploaded this chunk
|
||||
tex_height: (alloc_frames as u32 + TEX_WIDTH - 1) / TEX_WIDTH,
|
||||
sample_rate,
|
||||
channels,
|
||||
|
|
@ -681,6 +700,7 @@ impl egui_wgpu::CallbackTrait for WaveformCallback {
|
|||
&upload.samples,
|
||||
upload.sample_rate,
|
||||
upload.channels,
|
||||
upload.frame_limit,
|
||||
);
|
||||
cmds.extend(new_cmds);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue