perf(wallpaper): improve thumbnail load parallelism
This commit is contained in:
parent
2d7b635007
commit
dafbe30920
3 changed files with 59 additions and 56 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue