const BAND_PIXELS: usize = 16_384;
pub(crate) struct Workers {
#[cfg(not(target_arch = "wasm32"))]
pool: Option<rayon::ThreadPool>,
threads: usize,
}
impl std::fmt::Debug for Workers {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Workers")
.field("threads", &self.threads)
.finish_non_exhaustive()
}
}
impl Workers {
pub(crate) fn new(threads: usize) -> Self {
let threads = threads.max(1);
#[cfg(not(target_arch = "wasm32"))]
{
let pool = (threads > 1)
.then(|| {
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.thread_name(|index| format!("alpha2-cpu-render-{index}"))
.build()
.ok()
})
.flatten();
let threads = if pool.is_some() { threads } else { 1 };
Self { pool, threads }
}
#[cfg(target_arch = "wasm32")]
{
let _ = threads;
Self { threads: 1 }
}
}
pub(crate) const fn threads(&self) -> usize {
self.threads
}
#[cfg_attr(target_arch = "wasm32", allow(clippy::unused_self))]
pub(crate) fn bands<T: Send>(
&self,
pixels: &mut [T],
width: usize,
draw: impl Fn(usize, &mut [T]) + Sync + Send,
) {
if width == 0 || pixels.is_empty() {
return;
}
let rows = band_rows(width);
#[cfg(not(target_arch = "wasm32"))]
if let Some(pool) = &self.pool {
use rayon::prelude::*;
pool.install(|| {
pixels
.par_chunks_mut(rows * width)
.enumerate()
.for_each(|(band, chunk)| wide(|| draw(band * rows, chunk)));
});
return;
}
for (band, chunk) in pixels.chunks_mut(rows * width).enumerate() {
wide(|| draw(band * rows, chunk));
}
}
#[cfg_attr(target_arch = "wasm32", allow(clippy::unused_self))]
pub(crate) fn map<T: Send>(
&self,
count: usize,
work: impl Fn(usize) -> T + Sync + Send,
) -> Vec<T> {
#[cfg(not(target_arch = "wasm32"))]
if let Some(pool) = &self.pool {
use rayon::prelude::*;
return pool.install(|| (0..count).into_par_iter().map(&work).collect());
}
(0..count).map(work).collect()
}
}
fn band_rows(width: usize) -> usize {
(BAND_PIXELS / width).clamp(1, 64)
}
#[inline]
fn wide<R>(work: impl FnOnce() -> R) -> R {
#[cfg(target_arch = "x86_64")]
if avx2::available() {
#[allow(unsafe_code)]
return unsafe { avx2::run(work) };
}
work()
}
#[cfg(target_arch = "x86_64")]
mod avx2 {
use std::sync::OnceLock;
pub(super) fn available() -> bool {
static AVAILABLE: OnceLock<bool> = OnceLock::new();
*AVAILABLE.get_or_init(|| {
std::arch::is_x86_feature_detected!("avx2")
&& std::arch::is_x86_feature_detected!("f16c")
})
}
#[allow(unsafe_code)]
#[target_feature(enable = "avx2,f16c")]
pub(super) unsafe fn run<R>(work: impl FnOnce() -> R) -> R {
work()
}
}