Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster wave #302

Merged
merged 3 commits into from
May 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
Loading