use super::tests::{camera, engine, represented_scene};
use crate::engine::Engine;
use crate::testing::MockDevice;
use molgfx_core::ColorScheme;
use molgfx_math::Rgba8;
fn writes_since(engine: &Engine<MockDevice>, before: usize) -> Vec<&'static str> {
let Ok(writes) = engine.device.log.writes.lock() else {
panic!("write log lock")
};
let Ok(buffers) = engine.device.log.buffers.lock() else {
panic!("buffer log lock")
};
writes[before..]
.iter()
.map(|(buffer, _, _, _)| {
buffers
.iter()
.find(|(id, _, _)| id == buffer)
.map_or("unknown", |(_, label, _)| *label)
})
.collect()
}
fn frame_writes(engine: &mut Engine<MockDevice>, scene: &molgfx_core::Scene) -> Vec<&'static str> {
let Ok(writes) = engine.device.log.writes.lock() else {
panic!("write log lock")
};
let before = writes.len();
drop(writes);
if let Err(error) = engine.render(scene, &camera()) {
panic!("frame renders: {error}")
}
writes_since(engine, before)
}
#[test]
fn a_colour_scheme_change_writes_only_fixed_size_uniforms() {
let mut scene = represented_scene(1, 1);
let Some((representation, _)) = scene.representations().next() else {
panic!("fixture has a representation")
};
let mut engine = engine();
if let Err(error) = engine.render(&scene, &camera()) {
panic!("initial frame renders: {error}")
}
let Ok(buffers_before) = engine.device.log.buffers.lock().map(|value| value.len()) else {
panic!("buffer log lock")
};
for scheme in [
ColorScheme::ByChain,
ColorScheme::ByResidue,
ColorScheme::BySecondaryStructure,
ColorScheme::Uniform(Rgba8::opaque(12, 34, 56)),
] {
let Some(value) = scene.representation_mut(representation) else {
panic!("representation resolves")
};
value.color = scheme;
let changed = frame_writes(&mut engine, &scene);
assert!(
changed.iter().all(|label| *label == "frame uniforms"
|| *label == "representation uniforms"
|| *label == "colour scheme uniforms"),
"{scheme:?} wrote unexpected buffers: {changed:?}"
);
}
let Ok(buffers_after) = engine.device.log.buffers.lock().map(|value| value.len()) else {
panic!("buffer log lock")
};
assert_eq!(
buffers_after, buffers_before,
"a scheme change must not allocate"
);
}
#[test]
fn representations_differing_only_in_colour_share_one_record_set() {
let mut counts = Vec::new();
for (index, scheme) in [
ColorScheme::ByElement,
ColorScheme::Uniform(Rgba8::opaque(200, 30, 40)),
]
.into_iter()
.enumerate()
{
let mut scene = represented_scene(1, 1);
let Some((representation, _)) = scene.representations().next() else {
panic!("fixture has a representation")
};
let Some(value) = scene.representation_mut(representation) else {
panic!("representation resolves")
};
value.color = scheme;
value.order = u16::try_from(index).unwrap_or(0);
let mut engine = engine();
if let Err(error) = engine.render(&scene, &camera()) {
panic!("frame renders: {error}")
}
let Ok(buffers) = engine.device.log.buffers.lock() else {
panic!("buffer log lock")
};
counts.push(
buffers
.iter()
.filter(|(_, label, _)| *label == "atom instances")
.count(),
);
}
assert!(
counts.iter().all(|count| *count == 1),
"each scene allocates exactly one atom record set: {counts:?}"
);
}
#[test]
fn the_gpu_scheme_palette_matches_the_cpu_palette() {
let scene = represented_scene(1, 1);
let Some((representation, _)) = scene.representations().next() else {
panic!("fixture has a representation")
};
let Some(value) = scene.representation(representation) else {
panic!("representation resolves")
};
let uniforms = crate::scene_gpu::color_uniforms::ColorUniforms::new(value, [0, 1]);
let palette = uniforms.palette_probe();
for (index, color) in molgfx_core::CATEGORICAL_COLORS.into_iter().enumerate() {
assert_eq!(
palette[9 + index].map(f32::to_bits),
lanes(color).map(f32::to_bits),
"categorical slot {index}"
);
}
for (class, color) in molgfx_core::SECONDARY_STRUCTURE_COLORS
.into_iter()
.enumerate()
{
assert_eq!(
palette[4 + class].map(f32::to_bits),
lanes(color).map(f32::to_bits),
"secondary slot {class}"
);
}
}
fn lanes(color: Rgba8) -> [f32; 4] {
[
f32::from(color.r) / 255.0,
f32::from(color.g) / 255.0,
f32::from(color.b) / 255.0,
f32::from(color.a) / 255.0,
]
}
#[test]
fn the_colour_block_is_written_with_the_active_scheme() {
let mut scene = represented_scene(1, 1);
let Some((representation, _)) = scene.representations().next() else {
panic!("fixture has a representation")
};
let Some(value) = scene.representation_mut(representation) else {
panic!("representation resolves")
};
value.color = ColorScheme::Uniform(Rgba8::opaque(12, 34, 56));
let mut engine = engine();
if let Err(error) = engine.render(&scene, &camera()) {
panic!("frame renders: {error}")
}
let Ok(buffers) = engine.device.log.buffers.lock() else {
panic!("buffer log lock")
};
let color_buffer = buffers
.iter()
.find(|(_, label, _)| *label == "colour scheme uniforms")
.map(|(id, _, _)| *id);
let Some(color_buffer) = color_buffer else {
panic!("the colour block is allocated")
};
drop(buffers);
let Ok(writes) = engine.device.log.write_payloads.lock() else {
panic!("payload log lock")
};
let Ok(write_log) = engine.device.log.writes.lock() else {
panic!("write log lock")
};
let Some(index) = write_log
.iter()
.position(|(buffer, _, _, _)| *buffer == color_buffer)
else {
panic!("the colour block is written at least once")
};
let Some(payload) = writes.get(index) else {
panic!("the colour block write captured its bytes")
};
let selector_at = 17 * 16;
let scheme = u32::from_le_bytes([
payload[selector_at],
payload[selector_at + 1],
payload[selector_at + 2],
payload[selector_at + 3],
]);
let packed = u32::from_le_bytes([
payload[selector_at + 4],
payload[selector_at + 5],
payload[selector_at + 6],
payload[selector_at + 7],
]);
assert_eq!(scheme, 5, "a uniform scheme selects tag 5");
assert_eq!(
packed,
u32::from_le_bytes([12, 34, 56, 255]),
"the colour travels packed"
);
}