Skip to content

Commit

Permalink
Merge pull request #302 from LGFae/faster-wave
Browse files Browse the repository at this point in the history
This finalizes our faster transitions implementation.
We can now remove rayon as a dependency for `swww-daemon`.
  • Loading branch information
LGFae authored May 5, 2024
2 parents c944bab + b4b63f4 commit 9733f3e
Showing 1 changed file with 98 additions and 116 deletions.
214 changes: 98 additions & 116 deletions daemon/src/animations/transitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use std::{
time::{Duration, Instant},
};

use rayon::prelude::*;

use log::debug;
use utils::ipc::{Position, TransitionType};

Expand Down Expand Up @@ -126,13 +124,7 @@ impl<'a> Transition<'a> {
for wallpaper in self.wallpapers.iter() {
wallpaper.canvas_change(|canvas| {
for (old, new) in canvas.iter_mut().zip(new_img) {
if old.abs_diff(*new) < step {
*old = *new;
} else if *old > *new {
*old -= step;
} else {
*old += step;
}
change_byte(step, old, new);
}
done = canvas == new_img;
});
Expand Down Expand Up @@ -172,56 +164,88 @@ impl<'a> Transition<'a> {
let screen_diag = ((width.pow(2) + height.pow(2)) as f64).sqrt();

let angle = self.angle.to_radians();
let (sin, cos) = angle.sin_cos();
let (scale_x, scale_y) = (self.wave.0 as f64, self.wave.1 as f64);

let circle_radius = screen_diag / 2.0;

let f = |x: f64| (x / scale_x).sin() * scale_y;

// graph: https://www.desmos.com/calculator/wunde042es
//
// checks if a pixel is to the left or right of the line
let is_low = |x: f64, y: f64, offset: f64| {
let x = x - center.0 as f64;
let y = y - center.1 as f64;

let lhs = y * angle.cos() - x * angle.sin();
let rhs = f(x * angle.cos() + y * angle.sin()) + circle_radius - offset;
lhs >= rhs
};
let lhs = y * sin - x * cos;

// find the offset to start the transition at
let mut offset = {
let mut offset = 0.0;
for x in 0..width {
for y in 0..height {
if is_low(x as f64, y as f64, offset) {
offset += 1.0;
break;
}
}
}
offset
let f = ((x * sin + y * cos) / scale_x).sin() * scale_y;
let rhs = f - circle_radius + offset / circle_radius;
lhs <= rhs
};
let max_offset = 2.0 * circle_radius - offset;

let mut offset = (sin.abs() * width as f64 + cos.abs() * height as f64) * 2.0;
let a = circle_radius * cos;
let b = circle_radius * sin;
let max_offset = circle_radius.pow(2) * 2.0;
let (width, height) = (width as usize, height as usize);

let (mut seq, start) = self.bezier_seq(offset as f32, max_offset as f32);

let step = self.step;
let channels = crate::pixel_format().channels() as usize;
let stride = width * channels;
while start.elapsed().as_secs_f64() < seq.duration() {
self.parallel_draw_all(channels, new_img, |i, old, new| {
let pix_x = i % width;
let pix_y = height - i / width;
if is_low(pix_x as f64, pix_y as f64, offset) {
change_byte(channels, step, old, new);
}
});
self.updt_wallpapers(&mut now);

offset = seq.now() as f64;
seq.advance_to(start.elapsed().as_secs_f64());

for wallpaper in self.wallpapers.iter() {
wallpaper.canvas_change(|canvas| {
// divide in 3 sections: the one we know will not be drawn to, the one we know
// WILL be drawn to, and the one we need to do a more expensive check on.
// We do this by creating 2 lines: the first tangential to the wave's peaks,
// the second to its valeys. In-between is where we have to do the more
// expensive checks
for line in 0..height {
let y = ((height - line) as f64 - center.1 as f64 - scale_y * sin) * b;
let x = (circle_radius.powi(2) - y - offset) / a
+ center.0 as f64
+ scale_y * cos;
let x = x.min(width as f64);
let (col_begin, col_end) = if a.is_sign_negative() {
(0usize, x as usize * channels)
} else {
(x as usize * channels, stride)
};
for col in col_begin..col_end {
let old = unsafe { canvas.get_unchecked_mut(line * stride + col) };
let new = unsafe { new_img.get_unchecked(line * stride + col) };
change_byte(step, old, new);
}
let old_x = x;
let y = ((height - line) as f64 - center.1 as f64 + scale_y * sin) * b;
let x = (circle_radius.powi(2) - y - offset) / a + center.0 as f64
- scale_y * cos;
let x = x.min(width as f64);
let (col_begin, col_end) = if old_x < x {
(old_x as usize, x as usize)
} else {
(x as usize, old_x as usize)
};
for col in col_begin..col_end {
if is_low(col as f64, line as f64, offset) {
let i = line * stride + col * channels;
for j in 0..channels {
let old = unsafe { canvas.get_unchecked_mut(i + j) };
let new = unsafe { new_img.get_unchecked(i + j) };
change_byte(step, old, new);
}
}
}
}
});
}

self.updt_wallpapers(&mut now);
}
self.step = 4 + self.step / 4;
self.simple(new_img)
Expand All @@ -241,39 +265,43 @@ impl<'a> Transition<'a> {

let mut offset = {
let (x, y) = angle.sin_cos();
(x.abs() * width as f64 / 2.0 + y.abs() * height as f64 / 2.0).abs()
(x.abs() * width as f64 + y.abs() * height as f64) * 2.0
};

// line formula: (x-h)*a + (y-k)*b + C = r^2
// https://www.desmos.com/calculator/vpvzk12yar
//
// checks if a pixel is to the left or right of the line
let is_low = |pix_x: f64, pix_y: f64, offset: f64, radius: f64| {
let a = radius * angle.cos();
let b = radius * angle.sin();
let x = pix_x - center.0 as f64;
let y = pix_y - center.1 as f64;
let res = x * a + y * b + offset;
res >= radius.pow(2)
};
let a = circle_radius * angle.cos();
let b = circle_radius * angle.sin();

let (width, height) = (width as usize, height as usize);
let (mut seq, start) = self.bezier_seq(0.0, max_offset as f32);
let (mut seq, start) = self.bezier_seq(offset as f32, max_offset as f32);

let step = self.step;
let channels = crate::pixel_format().channels() as usize;
let stride = width * channels;
while start.elapsed().as_secs_f64() < seq.duration() {
self.parallel_draw_all(channels, new_img, |i, old, new| {
let pix_x = i % width;
let pix_y = height - i / width;
if is_low(pix_x as f64, pix_y as f64, offset, circle_radius) {
change_byte(channels, step, old, new);
}
});
self.updt_wallpapers(&mut now);

offset = seq.now() as f64;
seq.advance_to(start.elapsed().as_secs_f64());
for wallpaper in self.wallpapers.iter() {
wallpaper.canvas_change(|canvas| {
// line formula: (x-h)*a + (y-k)*b + C = r^2
// https://www.desmos.com/calculator/vpvzk12yar
for line in 0..height {
let y = ((height - line) as f64 - center.1 as f64) * b;
let x = (circle_radius.powi(2) - y - offset) / a + center.0 as f64;
let x = x.min(width as f64);
let (col_begin, col_end) = if a.is_sign_negative() {
(0usize, x as usize * channels)
} else {
(x as usize * channels, stride)
};
for col in col_begin..col_end {
let old = unsafe { canvas.get_unchecked_mut(line * stride + col) };
let new = unsafe { new_img.get_unchecked(line * stride + col) };
change_byte(step, old, new);
}
}
});
}
self.updt_wallpapers(&mut now);
}
self.step = 4 + self.step / 4;
self.simple(new_img)
Expand Down Expand Up @@ -318,13 +346,7 @@ impl<'a> Transition<'a> {
for col in col_begin..col_end {
let old = unsafe { canvas.get_unchecked_mut(line * stride + col) };
let new = unsafe { new_img.get_unchecked(line * stride + col) };
if old.abs_diff(*new) < step {
*old = *new;
} else if *old > *new {
*old -= step;
} else {
*old += step;
}
change_byte(step, old, new);
}
}
});
Expand Down Expand Up @@ -372,24 +394,12 @@ impl<'a> Transition<'a> {
for col in 0..col_begin {
let old = unsafe { canvas.get_unchecked_mut(line * stride + col) };
let new = unsafe { new_img.get_unchecked(line * stride + col) };
if old.abs_diff(*new) < step {
*old = *new;
} else if *old > *new {
*old -= step;
} else {
*old += step;
}
change_byte(step, old, new);
}
for col in col_end..stride {
let old = unsafe { canvas.get_unchecked_mut(line * stride + col) };
let new = unsafe { new_img.get_unchecked(line * stride + col) };
if old.abs_diff(*new) < step {
*old = *new;
} else if *old > *new {
*old -= step;
} else {
*old += step;
}
change_byte(step, old, new);
}
}
});
Expand All @@ -402,43 +412,15 @@ impl<'a> Transition<'a> {
self.step = 4 + self.step / 4;
self.simple(new_img)
}

/// Runs pixels_change_fn for every byte in the old img, in parallel
#[inline(always)]
fn parallel_draw_all<F>(&self, channels: usize, new_img: &[u8], f: F)
where
F: FnOnce(usize, &mut [u8], &[u8]) + Copy + Sync,
{
self.wallpapers.iter().for_each(|wallpaper| {
wallpaper.canvas_change(|canvas| {
canvas
.par_chunks_exact_mut(channels)
.zip_eq(new_img.par_chunks_exact(channels))
.enumerate()
.for_each(|(i, (old, new))| f(i, old, new));
});
});
}
}

#[inline(always)]
fn change_byte(channels: usize, step: u8, old: &mut [u8], new: &[u8]) {
// this check improves the assembly generation slightly, by making the compiler not assume
// channels can be arbitrarily large
if channels != 3 && channels != 4 {
log::error!("weird channel size of: {channels}");
return;
}

for i in 0..channels {
let old = unsafe { old.get_unchecked_mut(i) };
let new = unsafe { new.get_unchecked(i) };
if old.abs_diff(*new) < step {
*old = *new;
} else if *old > *new {
*old -= step;
} else {
*old += step;
}
fn change_byte(step: u8, old: &mut u8, new: &u8) {
if old.abs_diff(*new) < step {
*old = *new;
} else if *old > *new {
*old -= step;
} else {
*old += step;
}
}

0 comments on commit 9733f3e

Please sign in to comment.