use crate::{CancelToken, ChunkResult, SampleRange, WorkerLane};
use glam::Vec3;
use indicatrix_net::SceneState;
use std::{
sync::{
Mutex, PoisonError,
atomic::{AtomicU32, Ordering},
},
thread,
time::Duration,
};
pub(super) fn scene(width: u32, height: u32) -> SceneState {
use indicatrix::{
geometry::cuts::StandardGemCuts,
optics::{materials::GemMaterial, raytracer::LightingPreset},
};
SceneState {
width,
height,
yaw: 0.4,
pitch: 0.3,
distance: 3.0,
light_yaw: 0.85,
light_pitch: 0.95,
exposure: 1.0,
max_bounces: 4,
lighting_preset: LightingPreset::Daylight,
material: GemMaterial::diamond(),
planes: StandardGemCuts::standard_round_brilliant(),
girdle_frosted: false,
backdrop: 0.0,
environment: indicatrix_net::scene::SceneEnvironment::Studio,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Values {
Integer,
Fractional,
}
pub(super) fn value(values: Values, pixel: u32, sample: u32) -> Vec3 {
match values {
Values::Integer => Vec3::new(
((pixel * 7 + sample) % 13) as f32,
((pixel + sample * 3) % 5) as f32,
(sample % 4) as f32,
),
Values::Fractional => {
let h = pixel
.wrapping_mul(0x9e37_79b9)
.wrapping_add(sample.wrapping_mul(0x85eb_ca6b))
.rotate_left(13);
let scale = ((sample % 1000) as f32).mul_add(37.0, 1.0);
Vec3::new(
(h % 1_000_003) as f32 / 997.0 * scale,
(h % 7919) as f32 / 13.0,
(h >> 20) as f32 * 1e-3,
)
}
}
}
pub(super) fn chunk_sum(values: Values, pixels: usize, range: SampleRange, done: u32) -> Vec<Vec3> {
let mut sum = vec![Vec3::ZERO; pixels];
for sample in range.first_sample..range.first_sample + done {
for (pixel, dst) in sum.iter_mut().enumerate() {
*dst += value(values, pixel as u32, sample);
}
}
sum
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Failure {
Never,
Always {
after: u32,
},
First {
chunks: u32,
after: u32,
},
Panic,
}
pub(super) struct FakeLane {
name: String,
values: Values,
per_sample: Duration,
failure: Failure,
chunks_seen: AtomicU32,
traced: Mutex<Vec<u32>>,
}
impl FakeLane {
pub(super) fn new(name: &str, values: Values, per_sample: Duration, failure: Failure) -> Self {
Self {
name: name.to_owned(),
values,
per_sample,
failure,
chunks_seen: AtomicU32::new(0),
traced: Mutex::new(Vec::new()),
}
}
pub(super) fn traced(&self) -> Vec<u32> {
self.traced
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
fn limit(&self, chunk_number: u32, range: SampleRange) -> Option<u32> {
match self.failure {
Failure::Always { after } => Some(after),
Failure::First { chunks, after } if chunk_number < chunks => Some(after),
Failure::Never | Failure::First { .. } | Failure::Panic => None,
}
.filter(|&after| after < range.samples)
}
}
impl WorkerLane for FakeLane {
fn name(&self) -> &str {
&self.name
}
fn render_chunk(
&self,
scene: &SceneState,
range: SampleRange,
cancel: &CancelToken,
) -> ChunkResult {
let chunk_number = self.chunks_seen.fetch_add(1, Ordering::Relaxed);
assert!(
self.failure != Failure::Panic,
"fake lane {} panics",
self.name
);
let limit = self.limit(chunk_number, range);
let pixels = scene.width as usize * scene.height as usize;
let mut done = 0;
for _ in 0..limit.unwrap_or(range.samples) {
if cancel.is_cancelled() {
break;
}
if !self.per_sample.is_zero() {
thread::sleep(self.per_sample);
}
done += 1;
}
let sum = chunk_sum(self.values, pixels, range, done);
self.traced
.lock()
.unwrap_or_else(PoisonError::into_inner)
.extend(range.first_sample..range.first_sample + done);
if done == range.samples {
ChunkResult::complete(sum, done, None)
} else if cancel.is_cancelled() {
ChunkResult::partial(sum, done, "cancelled".to_owned())
} else {
ChunkResult::partial(sum, done, format!("{} dropped the connection", self.name))
}
}
}