use super::*;
fn srgb_to_linear(byte: u8) -> f32 {
let encoded = f32::from(byte) / 255.0;
if encoded <= 0.04045 {
encoded / 12.92
} else {
((encoded + 0.055) / 1.055).powf(2.4)
}
}
fn linear_to_srgb(linear: f32) -> u8 {
let linear = linear.clamp(0.0, 1.0);
let encoded = if linear <= 0.003_130_8 {
linear * 12.92
} else {
1.055 * linear.powf(1.0 / 2.4) - 0.055
};
(encoded * 255.0).round() as u8
}
fn pane(color: Color) -> Panes {
Panes::new(vec![(0.0, Material::color(color))])
}
#[test]
fn overlapping_translucent_draws_composite_back_to_front_from_either_order() {
let far = (-0.5, Material::color(Color::rgba(1.0, 0.0, 0.0, 0.5)));
let near = (0.5, Material::color(Color::rgba(0.0, 0.0, 1.0, 0.5)));
let read = |panes| rendered(raw("headless transparency"), panes);
let Some(as_drawn) = read(Panes::new(vec![far, near])) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(reversed) = read(Panes::new(vec![near, far])) else {
return;
};
assert_eq!(
middle(&as_drawn),
middle(&reversed),
"the pass sorts what the frame did not"
);
let background = pixel(&as_drawn, 0, 0);
let over = |channel: usize, far_channel: f32, near_channel: f32| {
let bg = srgb_to_linear(background[channel]);
let past_far = far_channel * 0.5 + bg * 0.5;
linear_to_srgb(near_channel * 0.5 + past_far * 0.5)
};
let composite = [
over(0, 1.0, 0.0),
over(1, 0.0, 0.0),
over(2, 0.0, 1.0),
u8::MAX,
];
let blended = middle(&as_drawn);
assert!(
blended
.iter()
.zip(composite)
.all(|(&channel, expected)| channel.abs_diff(expected) <= 1),
"half the near square over half the far one over the frame's own \
background: {blended:?} against {composite:?}"
);
}
#[test]
fn overlapping_additive_draws_leave_the_same_sum_from_either_order() {
let far = (-0.5, Material::color(Color::rgb(0.25, 0.0, 0.0)).additive());
let near = (0.5, Material::color(Color::rgb(0.0, 0.0, 0.5)).additive());
let read = |panes| rendered(raw("headless additive"), panes);
let Some(as_drawn) = read(Panes::new(vec![far, near])) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(reversed) = read(Panes::new(vec![near, far])) else {
return;
};
assert_eq!(
middle(&as_drawn),
middle(&reversed),
"addition takes them in any order"
);
let background = pixel(&as_drawn, 0, 0);
let summed =
|channel: usize, added: f32| linear_to_srgb(srgb_to_linear(background[channel]) + added);
let expected = [summed(0, 0.25), summed(1, 0.0), summed(2, 0.5), u8::MAX];
let blended = middle(&as_drawn);
assert!(
blended
.iter()
.zip(expected)
.all(|(&channel, expected)| channel.abs_diff(expected) <= 1),
"and leaves the two of them added onto the frame's own background: \
{blended:?} against {expected:?}"
);
}
#[test]
fn an_additive_draw_never_reads_back_darker_than_what_it_covers() {
let ground = (-0.5, Material::color(Color::rgb(0.5, 0.25, 0.125)));
let read = |panes| Some(middle(&rendered(raw("headless additive"), panes)?));
let over = |color| {
read(Panes::new(vec![
ground,
(0.5, Material::color(color).additive()),
]))
};
let Some(alone) = read(Panes::new(vec![ground])) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(under_black) = over(Color::BLACK) else {
return;
};
let Some(under_light) = over(Color::rgba(1.0, 1.0, 1.0, 0.25)) else {
return;
};
let Some(scaled) = read(pane(Color::rgb(0.75, 0.5, 0.375))) else {
return;
};
assert_eq!(under_black, alone, "a draw with no light in it adds none");
assert!(
under_light
.iter()
.zip(alone)
.all(|(over, under)| *over >= under),
"and one with light in it only ever adds: {under_light:?} over {alone:?}"
);
assert_eq!(under_light, scaled, "as much of it as the alpha asked for");
}