#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ResourceId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResourceAccess {
Read,
Write,
}
impl ResourceAccess {
pub fn conflicts_with(self, other: ResourceAccess) -> bool {
!matches!((self, other), (ResourceAccess::Read, ResourceAccess::Read))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResourceKind {
Image {
format: wgpu::TextureFormat,
width: u32,
height: u32,
mip_level_count: u32,
usage: wgpu::TextureUsages,
},
Buffer {
size: u64,
usage: wgpu::BufferUsages,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResourceLifetime {
Frame,
Persistent,
}
#[derive(Debug, Clone)]
pub struct ResourceDescriptor {
pub label: Option<String>,
pub kind: ResourceKind,
pub lifetime: ResourceLifetime,
}
#[cfg(test)]
mod p1_20_hazard_tracking_tests {
use super::ResourceAccess;
#[test]
fn read_read_does_not_conflict() {
assert!(!ResourceAccess::Read.conflicts_with(ResourceAccess::Read));
}
#[test]
fn read_write_conflicts_war() {
assert!(ResourceAccess::Read.conflicts_with(ResourceAccess::Write));
}
#[test]
fn write_read_conflicts_raw() {
assert!(ResourceAccess::Write.conflicts_with(ResourceAccess::Read));
}
#[test]
fn write_write_conflicts_waw() {
assert!(ResourceAccess::Write.conflicts_with(ResourceAccess::Write));
}
#[test]
fn conflict_table_is_symmetric() {
for a in [ResourceAccess::Read, ResourceAccess::Write] {
for b in [ResourceAccess::Read, ResourceAccess::Write] {
assert_eq!(
a.conflicts_with(b),
b.conflicts_with(a),
"conflict table not symmetric for {a:?} vs {b:?}"
);
}
}
}
}
#[cfg(test)]
mod p1_25_material_id_consistency_tests {
const RUST_GLASS: u32 = 7;
const RUST_DROP_SHADOW: u32 = 18;
const RUST_MESH_3D: u32 = 21;
fn scan_wgsl_for_material_ids(source: &str) -> std::collections::HashSet<u32> {
let mut ids = std::collections::HashSet::new();
let bytes = source.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i].is_ascii_digit() {
let start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
let num_str = std::str::from_utf8(&bytes[start..i]).unwrap();
if let Ok(n) = num_str.parse::<u32>() {
let mut j = i;
while j < bytes.len() && bytes[j].is_ascii_whitespace() {
j += 1;
}
if j < bytes.len() && bytes[j] == b'u' {
ids.insert(n);
}
}
} else {
i += 1;
}
}
ids
}
#[test]
fn glass_id_appears_in_wgsl() {
let source = include_str!("../shaders/material_opaque.wgsl");
let ids = scan_wgsl_for_material_ids(source);
assert!(
ids.contains(&RUST_GLASS),
"WGSL must reference material_id {RUST_GLASS} (GLASS)"
);
}
#[test]
fn drop_shadow_id_appears_in_wgsl() {
let source = include_str!("../shaders/material_opaque.wgsl");
let ids = scan_wgsl_for_material_ids(source);
assert!(
ids.contains(&RUST_DROP_SHADOW),
"WGSL must reference material_id {RUST_DROP_SHADOW} (DROP_SHADOW)"
);
}
#[test]
fn mesh_3d_id_appears_in_wgsl() {
let source = include_str!("../shaders/material_opaque.wgsl");
let ids = scan_wgsl_for_material_ids(source);
assert!(
ids.contains(&RUST_MESH_3D),
"WGSL must reference material_id {RUST_MESH_3D} (MESH_3D)"
);
}
#[test]
fn scanner_works() {
let src = "if (in.material_id == 18u) { } else if (in.material_id == 21u) { }";
let ids = scan_wgsl_for_material_ids(src);
assert!(ids.contains(&18));
assert!(ids.contains(&21));
let src2 = "let x = 18;";
let ids2 = scan_wgsl_for_material_ids(src2);
assert!(!ids2.contains(&18));
}
}
#[cfg(test)]
mod p2_7_scissor_rect_tests {
fn compute_scissor(
rect: Option<(f32, f32, f32, f32)>, scale: f32,
rt_w: i32,
rt_h: i32,
) -> Option<(u32, u32, u32, u32)> {
let (x, y, w, h) = rect?;
if rt_w <= 0 || rt_h <= 0 {
return None;
}
let x1 = (x * scale).round() as i32;
let y1 = (y * scale).round() as i32;
let x2 = ((x + w) * scale).round() as i32;
let y2 = ((y + h) * scale).round() as i32;
let sw = (x2 - x1).clamp(0, rt_w);
let sh = (y2 - y1).clamp(0, rt_h);
if sw > 0 && sh > 0 {
Some((x1 as u32, y1 as u32, sw as u32, sh as u32))
} else {
Some((0, 0, 0, 0))
}
}
#[test]
fn normal_rect_produces_correct_scissor() {
let sc = compute_scissor(Some((10.0, 20.0, 100.0, 50.0)), 1.0, 800, 600);
assert_eq!(sc, Some((10, 20, 100, 50)));
}
#[test]
fn zero_width_rect_produces_zero_scissor() {
let sc = compute_scissor(Some((10.0, 20.0, 0.0, 50.0)), 1.0, 800, 600);
assert_eq!(sc, Some((0, 0, 0, 0)));
}
#[test]
fn zero_height_rect_produces_zero_scissor() {
let sc = compute_scissor(Some((10.0, 20.0, 50.0, 0.0)), 1.0, 800, 600);
assert_eq!(sc, Some((0, 0, 0, 0)));
}
#[test]
fn negative_dimensions_clamp_to_zero() {
let sc = compute_scissor(Some((100.0, 100.0, -50.0, 50.0)), 1.0, 800, 600);
assert_eq!(sc, Some((0, 0, 0, 0)));
}
#[test]
fn none_rect_returns_none() {
let sc = compute_scissor(None, 1.0, 800, 600);
assert_eq!(sc, None);
}
#[test]
fn scissor_clamps_to_render_target() {
let sc = compute_scissor(Some((700.0, 500.0, 200.0, 200.0)), 1.0, 800, 600);
assert_eq!(sc, Some((700, 500, 200, 200)));
}
#[test]
fn scissor_extending_past_target_clamps() {
let sc = compute_scissor(Some((700.0, 500.0, 500.0, 500.0)), 1.0, 800, 600);
assert_eq!(sc, Some((700, 500, 500, 500)));
}
}