@@ -11,6 +11,9 @@ use crate::config::ScaleMode; |
| 11 | 11 | use crate::media::{scale_image_fast, AnimatedGif}; |
| 12 | 12 | use crate::x11::{AnimationRenderer, Connection}; |
| 13 | 13 | |
| 14 | +/// Default memory budget for pre-scaled animation frames (256 MB) |
| 15 | +const DEFAULT_MEMORY_BUDGET: u64 = 256 * 1024 * 1024; |
| 16 | + |
| 14 | 17 | /// Configuration for the animation loop |
| 15 | 18 | #[derive(Debug, Clone)] |
| 16 | 19 | pub struct AnimationConfig { |
@@ -38,8 +41,13 @@ pub struct AnimationLoop { |
| 38 | 41 | gif: AnimatedGif, |
| 39 | 42 | /// X11 animation renderer |
| 40 | 43 | renderer: AnimationRenderer, |
| 41 | | - /// Pre-scaled frames (cached) |
| 44 | + /// Pre-scaled frames (empty if streaming mode) |
| 42 | 45 | scaled_frames: Vec<image::RgbaImage>, |
| 46 | + /// Whether to scale frames on-the-fly instead of pre-scaling |
| 47 | + streaming: bool, |
| 48 | + /// Screen dimensions (used for on-the-fly scaling) |
| 49 | + screen_width: u32, |
| 50 | + screen_height: u32, |
| 43 | 51 | /// Configuration |
| 44 | 52 | config: AnimationConfig, |
| 45 | 53 | /// Whether the animation is paused |
@@ -57,25 +65,42 @@ impl AnimationLoop { |
| 57 | 65 | ) -> Result<Self> { |
| 58 | 66 | let renderer = AnimationRenderer::new(conn)?; |
| 59 | 67 | let (screen_width, screen_height) = renderer.dimensions(); |
| 60 | | - |
| 61 | | - // Pre-scale all frames |
| 62 | | - let scaled_frames: Vec<image::RgbaImage> = gif |
| 63 | | - .frames() |
| 64 | | - .iter() |
| 65 | | - .map(|frame| { |
| 66 | | - scale_image_fast( |
| 67 | | - &frame.image, |
| 68 | | - screen_width as u32, |
| 69 | | - screen_height as u32, |
| 70 | | - config.scale_mode, |
| 71 | | - ) |
| 72 | | - }) |
| 73 | | - .collect(); |
| 68 | + let sw = screen_width as u32; |
| 69 | + let sh = screen_height as u32; |
| 70 | + |
| 71 | + let per_frame_bytes = sw as u64 * sh as u64 * 4; |
| 72 | + let total_bytes = per_frame_bytes * gif.frame_count() as u64; |
| 73 | + |
| 74 | + let (scaled_frames, streaming) = if total_bytes <= DEFAULT_MEMORY_BUDGET { |
| 75 | + tracing::info!( |
| 76 | + "Animation fits in memory budget ({:.1} MB), pre-scaling all {} frames", |
| 77 | + total_bytes as f64 / (1024.0 * 1024.0), |
| 78 | + gif.frame_count(), |
| 79 | + ); |
| 80 | + let frames: Vec<image::RgbaImage> = gif |
| 81 | + .frames() |
| 82 | + .iter() |
| 83 | + .map(|frame| { |
| 84 | + scale_image_fast(&frame.image, sw, sh, config.scale_mode) |
| 85 | + }) |
| 86 | + .collect(); |
| 87 | + (frames, false) |
| 88 | + } else { |
| 89 | + tracing::info!( |
| 90 | + "Animation exceeds memory budget ({:.1} MB > {:.1} MB), scaling frames on-the-fly", |
| 91 | + total_bytes as f64 / (1024.0 * 1024.0), |
| 92 | + DEFAULT_MEMORY_BUDGET as f64 / (1024.0 * 1024.0), |
| 93 | + ); |
| 94 | + (Vec::new(), true) |
| 95 | + }; |
| 74 | 96 | |
| 75 | 97 | Ok(Self { |
| 76 | 98 | gif, |
| 77 | 99 | renderer, |
| 78 | 100 | scaled_frames, |
| 101 | + streaming, |
| 102 | + screen_width: sw, |
| 103 | + screen_height: sh, |
| 79 | 104 | config, |
| 80 | 105 | paused: Arc::new(AtomicBool::new(false)), |
| 81 | 106 | stop: Arc::new(AtomicBool::new(false)), |
@@ -165,8 +190,18 @@ impl AnimationLoop { |
| 165 | 190 | } |
| 166 | 191 | |
| 167 | 192 | // Render current frame |
| 168 | | - let scaled_frame = &self.scaled_frames[frame_index]; |
| 169 | | - self.renderer.render_and_present(conn, scaled_frame)?; |
| 193 | + if self.streaming { |
| 194 | + let scaled = scale_image_fast( |
| 195 | + &self.gif.frames()[frame_index].image, |
| 196 | + self.screen_width, |
| 197 | + self.screen_height, |
| 198 | + self.config.scale_mode, |
| 199 | + ); |
| 200 | + self.renderer.render_and_present(conn, &scaled)?; |
| 201 | + } else { |
| 202 | + let scaled_frame = &self.scaled_frames[frame_index]; |
| 203 | + self.renderer.render_and_present(conn, scaled_frame)?; |
| 204 | + } |
| 170 | 205 | |
| 171 | 206 | // Get delay for current frame |
| 172 | 207 | let frame_delay = self.gif.frames()[frame_index].delay; |
@@ -199,10 +234,20 @@ impl AnimationLoop { |
| 199 | 234 | } |
| 200 | 235 | |
| 201 | 236 | let frame_index = self.gif.current_index(); |
| 202 | | - let scaled_frame = &self.scaled_frames[frame_index]; |
| 203 | 237 | |
| 204 | 238 | // Render current frame |
| 205 | | - self.renderer.render_and_present(conn, scaled_frame)?; |
| 239 | + if self.streaming { |
| 240 | + let scaled = scale_image_fast( |
| 241 | + &self.gif.frames()[frame_index].image, |
| 242 | + self.screen_width, |
| 243 | + self.screen_height, |
| 244 | + self.config.scale_mode, |
| 245 | + ); |
| 246 | + self.renderer.render_and_present(conn, &scaled)?; |
| 247 | + } else { |
| 248 | + let scaled_frame = &self.scaled_frames[frame_index]; |
| 249 | + self.renderer.render_and_present(conn, scaled_frame)?; |
| 250 | + } |
| 206 | 251 | |
| 207 | 252 | // Get delay for current frame |
| 208 | 253 | let delay = self.gif.current_frame().delay; |