perf(wallpaper): improve thumbnail load parallelism

This commit is contained in:
Michael Aaron Murphy 2024-03-12 06:48:05 +01:00
parent 2d7b635007
commit dafbe30920
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
3 changed files with 59 additions and 56 deletions

View file

@ -5,6 +5,7 @@ mod config;
pub mod widgets; pub mod widgets;
pub use config::Config; pub use config::Config;
use futures::StreamExt;
use url::Url; use url::Url;
use std::{ use std::{
@ -846,8 +847,7 @@ impl Page {
// Loads a single custom image and its thumbnail for display in the backgrounds view. // Loads a single custom image and its thumbnail for display in the backgrounds view.
return cosmic::command::future(async move { return cosmic::command::future(async move {
let result = let result = wallpaper::load_image_with_thumbnail(path);
wallpaper::load_image_with_thumbnail(&mut Vec::new(), path).await;
let message = Message::ImageAdd(result.map(Arc::new)); let message = Message::ImageAdd(result.map(Arc::new));
let page_message = crate::pages::Message::DesktopWallpaper(message); let page_message = crate::pages::Message::DesktopWallpaper(message);
@ -886,8 +886,7 @@ impl Page {
// Load preview images concurrently for each custom image stored in the on-disk config. // Load preview images concurrently for each custom image stored in the on-disk config.
return cosmic::command::batch(custom_images.iter().cloned().map(|path| { return cosmic::command::batch(custom_images.iter().cloned().map(|path| {
cosmic::command::future(async move { cosmic::command::future(async move {
let result = let result = wallpaper::load_image_with_thumbnail(path);
wallpaper::load_image_with_thumbnail(&mut Vec::new(), path).await;
Message::ImageAdd(result.map(Arc::new)).into() Message::ImageAdd(result.map(Arc::new)).into()
}) })
@ -1049,9 +1048,11 @@ impl Context {
pub async fn change_folder(current_folder: PathBuf) -> Context { pub async fn change_folder(current_folder: PathBuf) -> Context {
let mut update = Context::default(); let mut update = Context::default();
let mut wallpapers = wallpaper::load_each_from_path(current_folder); let mut start = std::time::Instant::now();
while let Some((path, display_image, selection_image)) = wallpapers.recv().await { let mut wallpapers = wallpaper::load_each_from_path(current_folder).await;
while let Some((path, display_image, selection_image)) = wallpapers.next().await {
let id = update.paths.insert(path); let id = update.paths.insert(path);
update.display_images.insert(id, display_image); update.display_images.insert(id, display_image);
@ -1065,6 +1066,9 @@ pub async fn change_folder(current_folder: PathBuf) -> Context {
update.selection_handles.insert(id, selection_handle); update.selection_handles.insert(id, selection_handle);
} }
let elapsed = std::time::Instant::now().duration_since(start);
tracing::info!(?elapsed, "time to load backgrounds");
update update
} }

View file

@ -12,8 +12,8 @@ cosmic-randr-shell = { workspace = true }
dirs = "5.0.1" dirs = "5.0.1"
freedesktop-icons = "0.2.5" freedesktop-icons = "0.2.5"
futures-lite = "2.2.0" futures-lite = "2.2.0"
futures-util = "0.3.30"
image = "0.24.8" image = "0.24.8"
infer = "0.15.0" infer = "0.15.0"
rayon = "1.8.1"
tokio = { version = "1.35.1", features = ["sync"] } tokio = { version = "1.35.1", features = ["sync"] }
tracing = "0.1.40" tracing = "0.1.40"

View file

@ -1,5 +1,6 @@
pub use cosmic_bg_config::{Color, Config, Entry, Gradient, ScalingMode, Source}; pub use cosmic_bg_config::{Color, Config, Entry, Gradient, ScalingMode, Source};
use futures_lite::{Future, Stream};
use image::{DynamicImage, ImageBuffer, Rgba, RgbaImage}; use image::{DynamicImage, ImageBuffer, Rgba, RgbaImage};
use std::{ use std::{
borrow::Cow, borrow::Cow,
@ -7,8 +8,8 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
io::Read, io::Read,
path::{Path, PathBuf}, path::{Path, PathBuf},
pin::Pin,
}; };
use tokio::sync::mpsc::{self, Receiver};
pub const DEFAULT_COLORS: &[Color] = &[ pub const DEFAULT_COLORS: &[Color] = &[
Color::Single([0.580, 0.922, 0.922]), Color::Single([0.580, 0.922, 0.922]),
@ -87,12 +88,10 @@ pub fn cache_dir() -> Option<PathBuf> {
/// Loads wallpapers in parallel by spawning tasks with a rayon thread pool. /// Loads wallpapers in parallel by spawning tasks with a rayon thread pool.
#[must_use] #[must_use]
pub fn load_each_from_path(path: PathBuf) -> Receiver<(PathBuf, RgbaImage, RgbaImage)> { pub async fn load_each_from_path(
let (tx, rx) = mpsc::channel(1); path: PathBuf,
) -> Pin<Box<dyn Send + Stream<Item = (PathBuf, RgbaImage, RgbaImage)>>> {
tokio::task::spawn(async move { let wallpapers = tokio::task::spawn_blocking(move || {
// Scratch space for storing images into.
let mut buffer = Vec::new();
// Directories to search recursively. // Directories to search recursively.
let mut paths = vec![path]; let mut paths = vec![path];
// Discovered image files that will be loaded as wallpapers. // Discovered image files that will be loaded as wallpapers.
@ -123,18 +122,24 @@ pub fn load_each_from_path(path: PathBuf) -> Receiver<(PathBuf, RgbaImage, RgbaI
} }
} }
for path in wallpapers { wallpapers
if let Some(value) = load_image_with_thumbnail(&mut buffer, path).await {
let _res = tx.send(value).await;
}
}
}); });
rx if let Ok(wallpapers) = wallpapers.await {
use futures_util::StreamExt;
let future = futures_util::stream::iter(wallpapers)
.map(|path| tokio::task::spawn_blocking(|| load_image_with_thumbnail(path)))
.buffered(4)
.filter_map(|value| async { value.ok()? });
Box::pin(future)
} else {
Box::pin(futures_lite::stream::empty())
}
} }
pub async fn load_image_with_thumbnail( #[must_use]
buffer: &mut Vec<u8>, pub fn load_image_with_thumbnail(
path: PathBuf, path: PathBuf,
) -> Option<( ) -> Option<(
PathBuf, PathBuf,
@ -142,54 +147,48 @@ pub async fn load_image_with_thumbnail(
ImageBuffer<Rgba<u8>, Vec<u8>>, ImageBuffer<Rgba<u8>, Vec<u8>>,
)> { )> {
let cache_dir = cache_dir(); let cache_dir = cache_dir();
let image_operation = load_thumbnail(buffer, cache_dir.as_deref(), &path); let image_operation = load_thumbnail(&mut Vec::new(), cache_dir.as_deref(), &path);
let (tx, rx) = tokio::sync::oneshot::channel();
if let Some(image_operation) = image_operation { if let Some(image_operation) = image_operation {
let tokio_handle = tokio::runtime::Handle::current(); let tokio_handle = tokio::runtime::Handle::current();
rayon::spawn_fifo(move || { let display_thumbnail = match image_operation {
let display_thumbnail = match image_operation { ImageOperation::Cached(thumbnail) => thumbnail.to_rgba8(),
ImageOperation::Cached(thumbnail) => thumbnail.to_rgba8(),
ImageOperation::GenerateThumbnail { path, image } => { ImageOperation::GenerateThumbnail { path, image } => {
let image = image.thumbnail(300, 169).to_rgba8(); let image = image.thumbnail(300, 169).to_rgba8();
if let Some(path) = path { if let Some(path) = path {
// Save thumbnail to disk without blocking. // Save thumbnail to disk without blocking.
tokio_handle.spawn_blocking({ tokio_handle.spawn_blocking({
let image = image.clone(); let image = image.clone();
move || { move || {
if let Err(why) = image.save(&path) { if let Err(why) = image.save(&path) {
tracing::error!(?path, ?why, "failed to save image thumbnail"); tracing::error!(?path, ?why, "failed to save image thumbnail");
let _res = std::fs::remove_file(&path); let _res = std::fs::remove_file(&path);
}
} }
}); }
} });
image
} }
};
let mut selection_thumbnail = image::imageops::resize( image
&display_thumbnail, }
158, };
105,
image::imageops::FilterType::Lanczos3,
);
round(&mut selection_thumbnail, [8, 8, 8, 8]); let mut selection_thumbnail = image::imageops::resize(
&display_thumbnail,
158,
105,
image::imageops::FilterType::Lanczos3,
);
let _res = tx.send(Some((path, display_thumbnail, selection_thumbnail))); round(&mut selection_thumbnail, [8, 8, 8, 8]);
});
Some((path, display_thumbnail, selection_thumbnail))
} else { } else {
let _res = tx.send(None); None
} }
rx.await.unwrap_or(None)
} }
enum ImageOperation { enum ImageOperation {