1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Validation is asserted, not eyeballed.
//!
//! A device created invalid can pass an entire suite while appearing to work,
//! so these tests turn the layer on and require the log to come back empty.
//! They skip where the layer is not installed, since a machine without the SDK
//! should still get a green run.
use impeller_hal::{Extent2D, PixelFormat, TextureDescriptor};
use impeller_hal_vulkan::{ContextConfig, DevicePreference, VulkanContext};
fn validated(device: DevicePreference) -> Option<VulkanContext> {
let ctx = VulkanContext::with_config(ContextConfig {
device,
validation: true,
})
.ok()?;
if !ctx.validation_active() {
eprintln!("skipping: validation layer not installed");
return None;
}
Some(ctx)
}
fn assert_clean(ctx: &VulkanContext, what: &str) {
let errors: Vec<_> = ctx
.validation_messages()
.into_iter()
.filter(|m| m.severity == impeller_hal_vulkan::ValidationSeverity::Error)
.collect();
assert!(
errors.is_empty(),
"{what} produced {} validation error(s):\n{}",
errors.len(),
errors
.iter()
.map(|e| format!(" [{}] {}", e.id, e.message))
.collect::<Vec<_>>()
.join("\n")
);
}
#[test]
fn device_creation_is_validation_clean() {
let Some(ctx) = validated(DevicePreference::Auto) else {
return;
};
// This is the test that would have caught the missing image-format-list
// dependency, which every pixel test passed straight through.
assert_clean(&ctx, "device creation");
assert!(ctx.validation_clean());
}
#[test]
fn allocate_clear_and_read_is_validation_clean() {
let Some(mut ctx) = validated(DevicePreference::Auto) else {
return;
};
let mut tex = ctx
.create_texture(&TextureDescriptor::offscreen(
Extent2D::new(32, 32),
PixelFormat::Rgba8Unorm,
))
.expect("texture");
ctx.clear_texture(&mut tex, [0.0, 1.0, 1.0, 1.0])
.expect("clear");
let pixels = ctx.read_texture(&mut tex).expect("readback");
ctx.destroy_texture(tex);
assert_eq!(&pixels[..4], &[0, 255, 255, 255]);
// Layout transitions and copy regions are exactly what the layer checks,
// so a clean log here is worth more than the pixel assertion above.
assert_clean(&ctx, "allocate, clear and read");
}
#[test]
fn the_software_reference_is_validation_clean() {
let Some(mut ctx) = validated(DevicePreference::Software) else {
return;
};
let mut tex = ctx
.create_texture(&TextureDescriptor::offscreen(
Extent2D::new(16, 16),
PixelFormat::Bgra8Unorm,
))
.expect("texture");
ctx.clear_texture(&mut tex, [1.0, 0.0, 0.0, 1.0])
.expect("clear");
ctx.destroy_texture(tex);
assert_clean(&ctx, "software reference");
}
#[test]
fn repeated_context_cycles_stay_clean() {
if validated(DevicePreference::Auto).is_none() {
return;
}
// Teardown ordering bugs tend to report on the second or third cycle.
for i in 0..3 {
let ctx = validated(DevicePreference::Auto).expect("layer was present");
assert_clean(&ctx, &format!("cycle {i}"));
}
}
#[test]
fn synchronization_validation_is_switched_on() {
// Core validation checks that each call is well formed. It does not check
// that one access is ordered against the next -- a missing barrier, a read
// of an image the GPU has not finished writing -- which is a separate
// feature the layer does not enable by default. It is the one that matters
// most here, because this renderer synchronizes explicitly rather than
// through a driver that hides it, and because a missing barrier renders
// correctly on the device it was written on and wrongly elsewhere.
//
// So every clean validation log in this suite means one of two quite
// different things depending on this, and nothing else would notice if it
// stopped being requested.
//
// What this asserts is that it was requested and the extension carrying the
// request was there, not that the layer honored it -- there is no way to
// ask the layer what it enabled. The behavioral evidence is elsewhere and
// is strong: turning this on immediately reported a real hazard in the
// swapchain path, which is the change that added this test. A probe that
// constructed a hazard on purpose was written first and then removed,
// because every hazard reachable through this HAL is now prevented by the
// fix -- which is the outcome wanted, and leaves nothing to provoke.
let Ok(ctx) = VulkanContext::with_config(ContextConfig {
device: DevicePreference::Auto,
validation: true,
}) else {
eprintln!("skipping: no Vulkan device");
return;
};
if !ctx.validation_active() {
eprintln!("skipping: the validation layer is unavailable");
return;
}
assert!(
ctx.sync_validation_active(),
"the validation layer is on but synchronization validation is not, so a \
clean log in this suite says only that every call was well formed"
);
}