atagen

monolith

public
Star0
Watch0
1d62be4ff79ce38908a8b8a526f4bc96a7721751
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/render-vk/tests/golden.rs

824bf02b6e55atagen2026-10-16 09:15 UTC1
//! Renders small scenes offscreen and checks pixels. Runs on whatever Vulkan
824bf02b6e55atagen2026-10-16 09:15 UTC2
//! driver is available. `nix flake check` forces lavapipe.
824bf02b6e55atagen2026-10-16 09:15 UTC3
47259c39793fatagen2026-10-17 10:13 UTC4
use monolith_render_vk::{Clip, VulkanImage, VulkanRenderer, VulkanTexture};
824bf02b6e55atagen2026-10-16 09:15 UTC5
use smithay::backend::allocator::Fourcc;
824bf02b6e55atagen2026-10-16 09:15 UTC6
use smithay::backend::renderer::{
5a760f013a02atagen2026-10-16 09:55 UTC7
    Bind, Color32F, ExportMem, Frame, ImportMem, Offscreen, Renderer,
824bf02b6e55atagen2026-10-16 09:15 UTC8
};
5a760f013a02atagen2026-10-16 09:55 UTC9
use smithay::utils::{Physical, Rectangle, Transform};
824bf02b6e55atagen2026-10-16 09:15 UTC10
824bf02b6e55atagen2026-10-16 09:15 UTC11
const W: i32 = 64;
824bf02b6e55atagen2026-10-16 09:15 UTC12
const H: i32 = 64;
824bf02b6e55atagen2026-10-16 09:15 UTC13
824bf02b6e55atagen2026-10-16 09:15 UTC14
type Pixel = [u8; 4];
824bf02b6e55atagen2026-10-16 09:15 UTC15
824bf02b6e55atagen2026-10-16 09:15 UTC16
const RED: Pixel = [255, 0, 0, 255];
824bf02b6e55atagen2026-10-16 09:15 UTC17
const GREEN: Pixel = [0, 255, 0, 255];
824bf02b6e55atagen2026-10-16 09:15 UTC18
const WHITE: Pixel = [255, 255, 255, 255];
824bf02b6e55atagen2026-10-16 09:15 UTC19
const BLACK: Pixel = [0, 0, 0, 255];
824bf02b6e55atagen2026-10-16 09:15 UTC20
824bf02b6e55atagen2026-10-16 09:15 UTC21
struct Scene {
824bf02b6e55atagen2026-10-16 09:15 UTC22
    renderer: VulkanRenderer,
824bf02b6e55atagen2026-10-16 09:15 UTC23
    image: VulkanImage,
824bf02b6e55atagen2026-10-16 09:15 UTC24
}
824bf02b6e55atagen2026-10-16 09:15 UTC25
824bf02b6e55atagen2026-10-16 09:15 UTC26
impl Scene {
824bf02b6e55atagen2026-10-16 09:15 UTC27
    fn new() -> Self {
824bf02b6e55atagen2026-10-16 09:15 UTC28
        let mut renderer = VulkanRenderer::new().expect("no usable Vulkan device");
824bf02b6e55atagen2026-10-16 09:15 UTC29
        let image = renderer
824bf02b6e55atagen2026-10-16 09:15 UTC30
            .create_buffer(Fourcc::Argb8888, (W, H).into())
824bf02b6e55atagen2026-10-16 09:15 UTC31
            .unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC32
        Self { renderer, image }
824bf02b6e55atagen2026-10-16 09:15 UTC33
    }
824bf02b6e55atagen2026-10-16 09:15 UTC34
824bf02b6e55atagen2026-10-16 09:15 UTC35
    fn draw(
824bf02b6e55atagen2026-10-16 09:15 UTC36
        &mut self,
824bf02b6e55atagen2026-10-16 09:15 UTC37
        transform: Transform,
824bf02b6e55atagen2026-10-16 09:15 UTC38
        f: impl FnOnce(&mut monolith_render_vk::VulkanFrame<'_, '_>),
824bf02b6e55atagen2026-10-16 09:15 UTC39
    ) {
824bf02b6e55atagen2026-10-16 09:15 UTC40
        let mut target = self.renderer.bind(&mut self.image).unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC41
        let mut frame = self
824bf02b6e55atagen2026-10-16 09:15 UTC42
            .renderer
824bf02b6e55atagen2026-10-16 09:15 UTC43
            .render(&mut target, (W, H).into(), transform)
824bf02b6e55atagen2026-10-16 09:15 UTC44
            .unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC45
        f(&mut frame);
824bf02b6e55atagen2026-10-16 09:15 UTC46
        frame.finish().unwrap().wait().unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC47
    }
824bf02b6e55atagen2026-10-16 09:15 UTC48
824bf02b6e55atagen2026-10-16 09:15 UTC49
    /// Pixels as RGBA, whatever the image's byte order.
824bf02b6e55atagen2026-10-16 09:15 UTC50
    fn pixels(&mut self) -> Vec<Pixel> {
824bf02b6e55atagen2026-10-16 09:15 UTC51
        let target = self.renderer.bind(&mut self.image).unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC52
        let mapping = self
824bf02b6e55atagen2026-10-16 09:15 UTC53
            .renderer
824bf02b6e55atagen2026-10-16 09:15 UTC54
            .copy_framebuffer(
824bf02b6e55atagen2026-10-16 09:15 UTC55
                &target,
824bf02b6e55atagen2026-10-16 09:15 UTC56
                Rectangle::from_size((W, H).into()),
824bf02b6e55atagen2026-10-16 09:15 UTC57
                Fourcc::Abgr8888,
824bf02b6e55atagen2026-10-16 09:15 UTC58
            )
824bf02b6e55atagen2026-10-16 09:15 UTC59
            .unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC60
        self.renderer
824bf02b6e55atagen2026-10-16 09:15 UTC61
            .map_texture(&mapping)
824bf02b6e55atagen2026-10-16 09:15 UTC62
            .unwrap()
824bf02b6e55atagen2026-10-16 09:15 UTC63
            .as_chunks::<4>()
824bf02b6e55atagen2026-10-16 09:15 UTC64
            .0
824bf02b6e55atagen2026-10-16 09:15 UTC65
            .to_vec()
824bf02b6e55atagen2026-10-16 09:15 UTC66
    }
824bf02b6e55atagen2026-10-16 09:15 UTC67
824bf02b6e55atagen2026-10-16 09:15 UTC68
    fn texture(&mut self, size: (i32, i32), rgba: &[Pixel]) -> VulkanTexture {
824bf02b6e55atagen2026-10-16 09:15 UTC69
        let bytes: Vec<u8> = rgba.iter().flatten().copied().collect();
824bf02b6e55atagen2026-10-16 09:15 UTC70
        self.renderer
824bf02b6e55atagen2026-10-16 09:15 UTC71
            .import_memory(&bytes, Fourcc::Abgr8888, size.into(), false)
824bf02b6e55atagen2026-10-16 09:15 UTC72
            .unwrap()
824bf02b6e55atagen2026-10-16 09:15 UTC73
    }
824bf02b6e55atagen2026-10-16 09:15 UTC74
}
824bf02b6e55atagen2026-10-16 09:15 UTC75
824bf02b6e55atagen2026-10-16 09:15 UTC76
fn at(pixels: &[Pixel], x: i32, y: i32) -> Pixel {
824bf02b6e55atagen2026-10-16 09:15 UTC77
    pixels[(y * W + x) as usize]
824bf02b6e55atagen2026-10-16 09:15 UTC78
}
824bf02b6e55atagen2026-10-16 09:15 UTC79
824bf02b6e55atagen2026-10-16 09:15 UTC80
fn full() -> Rectangle<i32, Physical> {
824bf02b6e55atagen2026-10-16 09:15 UTC81
    Rectangle::from_size((W, H).into())
824bf02b6e55atagen2026-10-16 09:15 UTC82
}
824bf02b6e55atagen2026-10-16 09:15 UTC83
824bf02b6e55atagen2026-10-16 09:15 UTC84
fn rgba(p: Pixel) -> Color32F {
824bf02b6e55atagen2026-10-16 09:15 UTC85
    p.map(|c| f32::from(c) / 255.0).into()
824bf02b6e55atagen2026-10-16 09:15 UTC86
}
824bf02b6e55atagen2026-10-16 09:15 UTC87
824bf02b6e55atagen2026-10-16 09:15 UTC88
#[test]
824bf02b6e55atagen2026-10-16 09:15 UTC89
fn partial_clear_keeps_previous_contents() {
824bf02b6e55atagen2026-10-16 09:15 UTC90
    let mut s = Scene::new();
824bf02b6e55atagen2026-10-16 09:15 UTC91
    s.draw(Transform::Normal, |f| {
824bf02b6e55atagen2026-10-16 09:15 UTC92
        f.clear(rgba(RED), &[full()]).unwrap()
824bf02b6e55atagen2026-10-16 09:15 UTC93
    });
824bf02b6e55atagen2026-10-16 09:15 UTC94
    s.draw(Transform::Normal, |f| {
824bf02b6e55atagen2026-10-16 09:15 UTC95
        f.clear(
824bf02b6e55atagen2026-10-16 09:15 UTC96
            rgba(GREEN),
824bf02b6e55atagen2026-10-16 09:15 UTC97
            &[Rectangle::new((0, 0).into(), (32, 64).into())],
824bf02b6e55atagen2026-10-16 09:15 UTC98
        )
824bf02b6e55atagen2026-10-16 09:15 UTC99
        .unwrap()
824bf02b6e55atagen2026-10-16 09:15 UTC100
    });
824bf02b6e55atagen2026-10-16 09:15 UTC101
    let px = s.pixels();
824bf02b6e55atagen2026-10-16 09:15 UTC102
    assert_eq!(at(&px, 0, 0), GREEN);
824bf02b6e55atagen2026-10-16 09:15 UTC103
    assert_eq!(at(&px, 63, 0), RED);
824bf02b6e55atagen2026-10-16 09:15 UTC104
}
824bf02b6e55atagen2026-10-16 09:15 UTC105
824bf02b6e55atagen2026-10-16 09:15 UTC106
#[test]
824bf02b6e55atagen2026-10-16 09:15 UTC107
fn alpha_blends_premultiplied() {
824bf02b6e55atagen2026-10-16 09:15 UTC108
    let mut s = Scene::new();
824bf02b6e55atagen2026-10-16 09:15 UTC109
    let tex = s.texture((1, 1), &[WHITE]);
824bf02b6e55atagen2026-10-16 09:15 UTC110
    s.draw(Transform::Normal, |f| {
824bf02b6e55atagen2026-10-16 09:15 UTC111
        f.clear(rgba(BLACK), &[full()]).unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC112
        f.render_texture_from_to(
824bf02b6e55atagen2026-10-16 09:15 UTC113
            &tex,
824bf02b6e55atagen2026-10-16 09:15 UTC114
            Rectangle::from_size((1.0, 1.0).into()),
824bf02b6e55atagen2026-10-16 09:15 UTC115
            full(),
824bf02b6e55atagen2026-10-16 09:15 UTC116
            &[full()],
824bf02b6e55atagen2026-10-16 09:15 UTC117
            &[],
824bf02b6e55atagen2026-10-16 09:15 UTC118
            Transform::Normal,
824bf02b6e55atagen2026-10-16 09:15 UTC119
            0.5,
824bf02b6e55atagen2026-10-16 09:15 UTC120
        )
824bf02b6e55atagen2026-10-16 09:15 UTC121
        .unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC122
    });
824bf02b6e55atagen2026-10-16 09:15 UTC123
    let p = at(&s.pixels(), 32, 32);
824bf02b6e55atagen2026-10-16 09:15 UTC124
    assert!(
824bf02b6e55atagen2026-10-16 09:15 UTC125
        (127..=129).contains(&p[0]) && p[0] == p[1] && p[1] == p[2],
824bf02b6e55atagen2026-10-16 09:15 UTC126
        "{p:?}"
824bf02b6e55atagen2026-10-16 09:15 UTC127
    );
824bf02b6e55atagen2026-10-16 09:15 UTC128
}
824bf02b6e55atagen2026-10-16 09:15 UTC129
824bf02b6e55atagen2026-10-16 09:15 UTC130
#[test]
824bf02b6e55atagen2026-10-16 09:15 UTC131
fn output_transform_moves_content() {
824bf02b6e55atagen2026-10-16 09:15 UTC132
    let mut s = Scene::new();
824bf02b6e55atagen2026-10-16 09:15 UTC133
    s.draw(Transform::_180, |f| {
824bf02b6e55atagen2026-10-16 09:15 UTC134
        f.clear(rgba(BLACK), &[full()]).unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC135
        let dst = Rectangle::new((0, 0).into(), (16, 16).into());
824bf02b6e55atagen2026-10-16 09:15 UTC136
        f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(GREEN))
824bf02b6e55atagen2026-10-16 09:15 UTC137
            .unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC138
    });
824bf02b6e55atagen2026-10-16 09:15 UTC139
    let px = s.pixels();
824bf02b6e55atagen2026-10-16 09:15 UTC140
    assert_eq!(at(&px, 0, 0), BLACK);
824bf02b6e55atagen2026-10-16 09:15 UTC141
    assert_eq!(at(&px, 63, 63), GREEN);
824bf02b6e55atagen2026-10-16 09:15 UTC142
}
824bf02b6e55atagen2026-10-16 09:15 UTC143
824bf02b6e55atagen2026-10-16 09:15 UTC144
#[test]
824bf02b6e55atagen2026-10-16 09:15 UTC145
fn solid_fill_in_the_corner_keeps_its_colour() {
824bf02b6e55atagen2026-10-16 09:15 UTC146
    let mut s = Scene::new();
824bf02b6e55atagen2026-10-16 09:15 UTC147
    s.draw(Transform::Normal, |f| {
824bf02b6e55atagen2026-10-16 09:15 UTC148
        f.clear(rgba(BLACK), &[full()]).unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC149
        let dst = Rectangle::new((48, 48).into(), (16, 16).into());
824bf02b6e55atagen2026-10-16 09:15 UTC150
        f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(RED))
824bf02b6e55atagen2026-10-16 09:15 UTC151
            .unwrap();
824bf02b6e55atagen2026-10-16 09:15 UTC152
    });
824bf02b6e55atagen2026-10-16 09:15 UTC153
    assert_eq!(at(&s.pixels(), 56, 56), RED);
824bf02b6e55atagen2026-10-16 09:15 UTC154
}
47259c39793fatagen2026-10-17 10:13 UTC155
47259c39793fatagen2026-10-17 10:13 UTC156
/// The radius follows its corner through an output rotation.
47259c39793fatagen2026-10-17 10:13 UTC157
#[test]
47259c39793fatagen2026-10-17 10:13 UTC158
fn clip_radius_stays_on_its_corner_when_rotated() {
47259c39793fatagen2026-10-17 10:13 UTC159
    let corners = [(0, 0), (W - 1, 0), (0, H - 1), (W - 1, H - 1)];
47259c39793fatagen2026-10-17 10:13 UTC160
    let mut s = Scene::new();
47259c39793fatagen2026-10-17 10:13 UTC161
    s.draw(Transform::_90, |f| {
47259c39793fatagen2026-10-17 10:13 UTC162
        f.clear(rgba(BLACK), &[full()]).unwrap();
47259c39793fatagen2026-10-17 10:13 UTC163
        let dst = Rectangle::new((0, 0).into(), (4, 4).into());
47259c39793fatagen2026-10-17 10:13 UTC164
        f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(GREEN))
47259c39793fatagen2026-10-17 10:13 UTC165
            .unwrap();
47259c39793fatagen2026-10-17 10:13 UTC166
    });
47259c39793fatagen2026-10-17 10:13 UTC167
    let px = s.pixels();
47259c39793fatagen2026-10-17 10:13 UTC168
    let top_left = corners
47259c39793fatagen2026-10-17 10:13 UTC169
        .into_iter()
47259c39793fatagen2026-10-17 10:13 UTC170
        .find(|&(x, y)| at(&px, x, y) == GREEN)
47259c39793fatagen2026-10-17 10:13 UTC171
        .unwrap();
47259c39793fatagen2026-10-17 10:13 UTC172
    s.draw(Transform::_90, |f| {
47259c39793fatagen2026-10-17 10:13 UTC173
        f.clear(rgba(BLACK), &[full()]).unwrap();
47259c39793fatagen2026-10-17 10:13 UTC174
        f.set_clip(Some(Clip {
47259c39793fatagen2026-10-17 10:13 UTC175
            rect: full(),
47259c39793fatagen2026-10-17 10:13 UTC176
            radii: [20.0, 0.0, 0.0, 0.0],
47259c39793fatagen2026-10-17 10:13 UTC177
            ring: 0.0,
47259c39793fatagen2026-10-17 10:13 UTC178
        }));
47259c39793fatagen2026-10-17 10:13 UTC179
        f.draw_solid(full(), &[full()], rgba(GREEN)).unwrap();
47259c39793fatagen2026-10-17 10:13 UTC180
    });
47259c39793fatagen2026-10-17 10:13 UTC181
    let px = s.pixels();
47259c39793fatagen2026-10-17 10:13 UTC182
    for (x, y) in corners {
47259c39793fatagen2026-10-17 10:13 UTC183
        let expected = if (x, y) == top_left { BLACK } else { GREEN };
47259c39793fatagen2026-10-17 10:13 UTC184
        assert_eq!(at(&px, x, y), expected, "corner {x},{y}");
47259c39793fatagen2026-10-17 10:13 UTC185
    }
47259c39793fatagen2026-10-17 10:13 UTC186
    assert_eq!(at(&px, W / 2, H / 2), GREEN);
47259c39793fatagen2026-10-17 10:13 UTC187
}
0b68ce424a32atagen2026-10-18 03:17 UTC188
0b68ce424a32atagen2026-10-18 03:17 UTC189
/// Draws arrive in the rotated space, which for a quarter turn on a wide
0b68ce424a32atagen2026-10-18 03:17 UTC190
/// output is tall, so its far corner has to land inside the framebuffer.
0b68ce424a32atagen2026-10-18 03:17 UTC191
#[test]
0b68ce424a32atagen2026-10-18 03:17 UTC192
fn quarter_turn_on_a_wide_output_fills_it() {
0b68ce424a32atagen2026-10-18 03:17 UTC193
    let (fw, fh) = (64, 32);
0b68ce424a32atagen2026-10-18 03:17 UTC194
    let mut renderer = VulkanRenderer::new().expect("no usable Vulkan device");
0b68ce424a32atagen2026-10-18 03:17 UTC195
    let mut image = renderer
0b68ce424a32atagen2026-10-18 03:17 UTC196
        .create_buffer(Fourcc::Argb8888, (fw, fh).into())
0b68ce424a32atagen2026-10-18 03:17 UTC197
        .unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC198
    for transform in [Transform::_90, Transform::_270, Transform::Flipped90] {
0b68ce424a32atagen2026-10-18 03:17 UTC199
        let mut target = renderer.bind(&mut image).unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC200
        let mut frame = renderer
0b68ce424a32atagen2026-10-18 03:17 UTC201
            .render(&mut target, (fw, fh).into(), transform)
0b68ce424a32atagen2026-10-18 03:17 UTC202
            .unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC203
        let area = Rectangle::from_size((fh, fw).into());
0b68ce424a32atagen2026-10-18 03:17 UTC204
        frame.clear(rgba(BLACK), &[area]).unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC205
        let near = Rectangle::new((0, 0).into(), (8, 8).into());
0b68ce424a32atagen2026-10-18 03:17 UTC206
        let far = Rectangle::new((fh - 8, fw - 8).into(), (8, 8).into());
0b68ce424a32atagen2026-10-18 03:17 UTC207
        frame
0b68ce424a32atagen2026-10-18 03:17 UTC208
            .draw_solid(near, &[Rectangle::from_size(near.size)], rgba(GREEN))
0b68ce424a32atagen2026-10-18 03:17 UTC209
            .unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC210
        frame
0b68ce424a32atagen2026-10-18 03:17 UTC211
            .draw_solid(far, &[Rectangle::from_size(far.size)], rgba(RED))
0b68ce424a32atagen2026-10-18 03:17 UTC212
            .unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC213
        frame.finish().unwrap().wait().unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC214
        drop(target);
0b68ce424a32atagen2026-10-18 03:17 UTC215
        let target = renderer.bind(&mut image).unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC216
        let mapping = renderer
0b68ce424a32atagen2026-10-18 03:17 UTC217
            .copy_framebuffer(
0b68ce424a32atagen2026-10-18 03:17 UTC218
                &target,
0b68ce424a32atagen2026-10-18 03:17 UTC219
                Rectangle::from_size((fw, fh).into()),
0b68ce424a32atagen2026-10-18 03:17 UTC220
                Fourcc::Abgr8888,
0b68ce424a32atagen2026-10-18 03:17 UTC221
            )
0b68ce424a32atagen2026-10-18 03:17 UTC222
            .unwrap();
0b68ce424a32atagen2026-10-18 03:17 UTC223
        let px = renderer
0b68ce424a32atagen2026-10-18 03:17 UTC224
            .map_texture(&mapping)
0b68ce424a32atagen2026-10-18 03:17 UTC225
            .unwrap()
0b68ce424a32atagen2026-10-18 03:17 UTC226
            .as_chunks::<4>()
0b68ce424a32atagen2026-10-18 03:17 UTC227
            .0
0b68ce424a32atagen2026-10-18 03:17 UTC228
            .to_vec();
0b68ce424a32atagen2026-10-18 03:17 UTC229
        let at = |x: i32, y: i32| px[(y * fw + x) as usize];
0b68ce424a32atagen2026-10-18 03:17 UTC230
        let corners = [(0, 0), (fw - 1, 0), (0, fh - 1), (fw - 1, fh - 1)];
0b68ce424a32atagen2026-10-18 03:17 UTC231
        let green: Vec<_> = corners
0b68ce424a32atagen2026-10-18 03:17 UTC232
            .iter()
0b68ce424a32atagen2026-10-18 03:17 UTC233
            .filter(|&&(x, y)| at(x, y) == GREEN)
0b68ce424a32atagen2026-10-18 03:17 UTC234
            .collect();
0b68ce424a32atagen2026-10-18 03:17 UTC235
        let red: Vec<_> = corners.iter().filter(|&&(x, y)| at(x, y) == RED).collect();
0b68ce424a32atagen2026-10-18 03:17 UTC236
        assert_eq!((green.len(), red.len()), (1, 1), "{transform:?}");
0b68ce424a32atagen2026-10-18 03:17 UTC237
        let (g, r) = (green[0], red[0]);
0b68ce424a32atagen2026-10-18 03:17 UTC238
        assert!(g.0 != r.0 && g.1 != r.1, "{transform:?} not diagonal");
0b68ce424a32atagen2026-10-18 03:17 UTC239
    }
0b68ce424a32atagen2026-10-18 03:17 UTC240
}