atagen

monolith

public
Star0
Watch0
9e8937bb429790b7377d92bcac1bd989d5fe703e
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/render-vk/tests/golden.rsRust240 lines7.5 KB
1
//! Renders small scenes offscreen and checks pixels. Runs on whatever Vulkan
2
//! driver is available. `nix flake check` forces lavapipe.
3
4
use monolith_render_vk::{Clip, VulkanImage, VulkanRenderer, VulkanTexture};
5
use smithay::backend::allocator::Fourcc;
6
use smithay::backend::renderer::{
7
    Bind, Color32F, ExportMem, Frame, ImportMem, Offscreen, Renderer,
8
};
9
use smithay::utils::{Physical, Rectangle, Transform};
10
11
const W: i32 = 64;
12
const H: i32 = 64;
13
14
type Pixel = [u8; 4];
15
16
const RED: Pixel = [255, 0, 0, 255];
17
const GREEN: Pixel = [0, 255, 0, 255];
18
const WHITE: Pixel = [255, 255, 255, 255];
19
const BLACK: Pixel = [0, 0, 0, 255];
20
21
struct Scene {
22
    renderer: VulkanRenderer,
23
    image: VulkanImage,
24
}
25
26
impl Scene {
27
    fn new() -> Self {
28
        let mut renderer = VulkanRenderer::new().expect("no usable Vulkan device");
29
        let image = renderer
30
            .create_buffer(Fourcc::Argb8888, (W, H).into())
31
            .unwrap();
32
        Self { renderer, image }
33
    }
34
35
    fn draw(
36
        &mut self,
37
        transform: Transform,
38
        f: impl FnOnce(&mut monolith_render_vk::VulkanFrame<'_, '_>),
39
    ) {
40
        let mut target = self.renderer.bind(&mut self.image).unwrap();
41
        let mut frame = self
42
            .renderer
43
            .render(&mut target, (W, H).into(), transform)
44
            .unwrap();
45
        f(&mut frame);
46
        frame.finish().unwrap().wait().unwrap();
47
    }
48
49
    /// Pixels as RGBA, whatever the image's byte order.
50
    fn pixels(&mut self) -> Vec<Pixel> {
51
        let target = self.renderer.bind(&mut self.image).unwrap();
52
        let mapping = self
53
            .renderer
54
            .copy_framebuffer(
55
                &target,
56
                Rectangle::from_size((W, H).into()),
57
                Fourcc::Abgr8888,
58
            )
59
            .unwrap();
60
        self.renderer
61
            .map_texture(&mapping)
62
            .unwrap()
63
            .as_chunks::<4>()
64
            .0
65
            .to_vec()
66
    }
67
68
    fn texture(&mut self, size: (i32, i32), rgba: &[Pixel]) -> VulkanTexture {
69
        let bytes: Vec<u8> = rgba.iter().flatten().copied().collect();
70
        self.renderer
71
            .import_memory(&bytes, Fourcc::Abgr8888, size.into(), false)
72
            .unwrap()
73
    }
74
}
75
76
fn at(pixels: &[Pixel], x: i32, y: i32) -> Pixel {
77
    pixels[(y * W + x) as usize]
78
}
79
80
fn full() -> Rectangle<i32, Physical> {
81
    Rectangle::from_size((W, H).into())
82
}
83
84
fn rgba(p: Pixel) -> Color32F {
85
    p.map(|c| f32::from(c) / 255.0).into()
86
}
87
88
#[test]
89
fn partial_clear_keeps_previous_contents() {
90
    let mut s = Scene::new();
91
    s.draw(Transform::Normal, |f| {
92
        f.clear(rgba(RED), &[full()]).unwrap()
93
    });
94
    s.draw(Transform::Normal, |f| {
95
        f.clear(
96
            rgba(GREEN),
97
            &[Rectangle::new((0, 0).into(), (32, 64).into())],
98
        )
99
        .unwrap()
100
    });
101
    let px = s.pixels();
102
    assert_eq!(at(&px, 0, 0), GREEN);
103
    assert_eq!(at(&px, 63, 0), RED);
104
}
105
106
#[test]
107
fn alpha_blends_premultiplied() {
108
    let mut s = Scene::new();
109
    let tex = s.texture((1, 1), &[WHITE]);
110
    s.draw(Transform::Normal, |f| {
111
        f.clear(rgba(BLACK), &[full()]).unwrap();
112
        f.render_texture_from_to(
113
            &tex,
114
            Rectangle::from_size((1.0, 1.0).into()),
115
            full(),
116
            &[full()],
117
            &[],
118
            Transform::Normal,
119
            0.5,
120
        )
121
        .unwrap();
122
    });
123
    let p = at(&s.pixels(), 32, 32);
124
    assert!(
125
        (127..=129).contains(&p[0]) && p[0] == p[1] && p[1] == p[2],
126
        "{p:?}"
127
    );
128
}
129
130
#[test]
131
fn output_transform_moves_content() {
132
    let mut s = Scene::new();
133
    s.draw(Transform::_180, |f| {
134
        f.clear(rgba(BLACK), &[full()]).unwrap();
135
        let dst = Rectangle::new((0, 0).into(), (16, 16).into());
136
        f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(GREEN))
137
            .unwrap();
138
    });
139
    let px = s.pixels();
140
    assert_eq!(at(&px, 0, 0), BLACK);
141
    assert_eq!(at(&px, 63, 63), GREEN);
142
}
143
144
#[test]
145
fn solid_fill_in_the_corner_keeps_its_colour() {
146
    let mut s = Scene::new();
147
    s.draw(Transform::Normal, |f| {
148
        f.clear(rgba(BLACK), &[full()]).unwrap();
149
        let dst = Rectangle::new((48, 48).into(), (16, 16).into());
150
        f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(RED))
151
            .unwrap();
152
    });
153
    assert_eq!(at(&s.pixels(), 56, 56), RED);
154
}
155
156
/// The radius follows its corner through an output rotation.
157
#[test]
158
fn clip_radius_stays_on_its_corner_when_rotated() {
159
    let corners = [(0, 0), (W - 1, 0), (0, H - 1), (W - 1, H - 1)];
160
    let mut s = Scene::new();
161
    s.draw(Transform::_90, |f| {
162
        f.clear(rgba(BLACK), &[full()]).unwrap();
163
        let dst = Rectangle::new((0, 0).into(), (4, 4).into());
164
        f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(GREEN))
165
            .unwrap();
166
    });
167
    let px = s.pixels();
168
    let top_left = corners
169
        .into_iter()
170
        .find(|&(x, y)| at(&px, x, y) == GREEN)
171
        .unwrap();
172
    s.draw(Transform::_90, |f| {
173
        f.clear(rgba(BLACK), &[full()]).unwrap();
174
        f.set_clip(Some(Clip {
175
            rect: full(),
176
            radii: [20.0, 0.0, 0.0, 0.0],
177
            ring: 0.0,
178
        }));
179
        f.draw_solid(full(), &[full()], rgba(GREEN)).unwrap();
180
    });
181
    let px = s.pixels();
182
    for (x, y) in corners {
183
        let expected = if (x, y) == top_left { BLACK } else { GREEN };
184
        assert_eq!(at(&px, x, y), expected, "corner {x},{y}");
185
    }
186
    assert_eq!(at(&px, W / 2, H / 2), GREEN);
187
}
188
189
/// Draws arrive in the rotated space, which for a quarter turn on a wide
190
/// output is tall, so its far corner has to land inside the framebuffer.
191
#[test]
192
fn quarter_turn_on_a_wide_output_fills_it() {
193
    let (fw, fh) = (64, 32);
194
    let mut renderer = VulkanRenderer::new().expect("no usable Vulkan device");
195
    let mut image = renderer
196
        .create_buffer(Fourcc::Argb8888, (fw, fh).into())
197
        .unwrap();
198
    for transform in [Transform::_90, Transform::_270, Transform::Flipped90] {
199
        let mut target = renderer.bind(&mut image).unwrap();
200
        let mut frame = renderer
201
            .render(&mut target, (fw, fh).into(), transform)
202
            .unwrap();
203
        let area = Rectangle::from_size((fh, fw).into());
204
        frame.clear(rgba(BLACK), &[area]).unwrap();
205
        let near = Rectangle::new((0, 0).into(), (8, 8).into());
206
        let far = Rectangle::new((fh - 8, fw - 8).into(), (8, 8).into());
207
        frame
208
            .draw_solid(near, &[Rectangle::from_size(near.size)], rgba(GREEN))
209
            .unwrap();
210
        frame
211
            .draw_solid(far, &[Rectangle::from_size(far.size)], rgba(RED))
212
            .unwrap();
213
        frame.finish().unwrap().wait().unwrap();
214
        drop(target);
215
        let target = renderer.bind(&mut image).unwrap();
216
        let mapping = renderer
217
            .copy_framebuffer(
218
                &target,
219
                Rectangle::from_size((fw, fh).into()),
220
                Fourcc::Abgr8888,
221
            )
222
            .unwrap();
223
        let px = renderer
224
            .map_texture(&mapping)
225
            .unwrap()
226
            .as_chunks::<4>()
227
            .0
228
            .to_vec();
229
        let at = |x: i32, y: i32| px[(y * fw + x) as usize];
230
        let corners = [(0, 0), (fw - 1, 0), (0, fh - 1), (fw - 1, fh - 1)];
231
        let green: Vec<_> = corners
232
            .iter()
233
            .filter(|&&(x, y)| at(x, y) == GREEN)
234
            .collect();
235
        let red: Vec<_> = corners.iter().filter(|&&(x, y)| at(x, y) == RED).collect();
236
        assert_eq!((green.len(), red.len()), (1, 1), "{transform:?}");
237
        let (g, r) = (green[0], red[0]);
238
        assert!(g.0 != r.0 && g.1 != r.1, "{transform:?} not diagonal");
239
    }
240
}