use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
const VULKAN_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/src/vulkan");
const DIRECTX_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/src/directx");
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Reason {
GraphDriven,
IntraPass,
Upload,
OutOfFrame,
Present,
Ungraphed,
Inline,
AttachmentLayout,
}
struct BackendAudit {
backend: &'static str,
root: &'static str,
calls: &'static [&'static str],
sites: &'static [(&'static str, &'static str, usize, Reason)],
}
const AUDITS: &[BackendAudit] = &[
BackendAudit {
backend: "vulkan",
root: VULKAN_ROOT,
calls: &["cmd_pipeline_barrier", ".final_layout("],
sites: &[
(
"graph_exec.rs",
"cmd_pipeline_barrier",
3,
Reason::GraphDriven,
),
("hiz.rs", "cmd_pipeline_barrier", 1, Reason::IntraPass),
("particle.rs", "cmd_pipeline_barrier", 3, Reason::IntraPass),
("texture.rs", "cmd_pipeline_barrier", 1, Reason::Upload),
("probe.rs", "cmd_pipeline_barrier", 3, Reason::OutOfFrame),
("raytrace.rs", "cmd_pipeline_barrier", 6, Reason::OutOfFrame),
(
"screenshot.rs",
"cmd_pipeline_barrier",
2,
Reason::OutOfFrame,
),
(
"auto_exposure.rs",
"cmd_pipeline_barrier",
4,
Reason::Ungraphed,
),
("cull.rs", "cmd_pipeline_barrier", 1, Reason::Ungraphed),
("planar.rs", "cmd_pipeline_barrier", 2, Reason::Ungraphed),
(
"transparent.rs",
"cmd_pipeline_barrier",
2,
Reason::IntraPass,
),
("raymarch.rs", "cmd_pipeline_barrier", 2, Reason::IntraPass),
("main.rs", "cmd_pipeline_barrier", 1, Reason::Inline),
(
"post/upscale/mod.rs",
"cmd_pipeline_barrier",
2,
Reason::Inline,
),
(
"render_pass.rs",
".final_layout(",
9,
Reason::AttachmentLayout,
),
("decal.rs", ".final_layout(", 1, Reason::AttachmentLayout),
("fog.rs", ".final_layout(", 1, Reason::AttachmentLayout),
(
"transparent.rs",
".final_layout(",
1,
Reason::AttachmentLayout,
),
("line.rs", ".final_layout(", 1, Reason::AttachmentLayout),
("particle.rs", ".final_layout(", 1, Reason::AttachmentLayout),
("raymarch.rs", ".final_layout(", 2, Reason::AttachmentLayout),
(
"post/gbuffer.rs",
".final_layout(",
4,
Reason::AttachmentLayout,
),
(
"post/reflection_composite.rs",
".final_layout(",
1,
Reason::AttachmentLayout,
),
(
"post/rt_reflections.rs",
".final_layout(",
1,
Reason::AttachmentLayout,
),
(
"post/ssao.rs",
".final_layout(",
2,
Reason::AttachmentLayout,
),
(
"post/ssgi.rs",
".final_layout(",
2,
Reason::AttachmentLayout,
),
("post/ssr.rs", ".final_layout(", 1, Reason::AttachmentLayout),
("post/taa.rs", ".final_layout(", 1, Reason::AttachmentLayout),
],
},
BackendAudit {
backend: "directx",
root: DIRECTX_ROOT,
calls: &[".ResourceBarrier("],
sites: &[
("graph_exec.rs", ".ResourceBarrier(", 5, Reason::GraphDriven),
("hiz.rs", ".ResourceBarrier(", 2, Reason::IntraPass),
("particle.rs", ".ResourceBarrier(", 6, Reason::IntraPass),
("draw/main.rs", ".ResourceBarrier(", 2, Reason::IntraPass),
("raymarch.rs", ".ResourceBarrier(", 4, Reason::IntraPass),
("transparent.rs", ".ResourceBarrier(", 2, Reason::IntraPass),
("post/ssgi.rs", ".ResourceBarrier(", 2, Reason::IntraPass),
("post/ssao.rs", ".ResourceBarrier(", 2, Reason::IntraPass),
(
"post/rt_reflections.rs",
".ResourceBarrier(",
2,
Reason::IntraPass,
),
("post/bloom.rs", ".ResourceBarrier(", 2, Reason::IntraPass),
(
"post/fullscreen.rs",
".ResourceBarrier(",
2,
Reason::IntraPass,
),
("allocator.rs", ".ResourceBarrier(", 1, Reason::Upload),
("resources.rs", ".ResourceBarrier(", 5, Reason::Upload),
("texture.rs", ".ResourceBarrier(", 6, Reason::Upload),
("transient_pool.rs", ".ResourceBarrier(", 2, Reason::Upload),
(
"geometry_rebuild.rs",
".ResourceBarrier(",
6,
Reason::OutOfFrame,
),
("probe.rs", ".ResourceBarrier(", 5, Reason::OutOfFrame),
("raytrace.rs", ".ResourceBarrier(", 11, Reason::OutOfFrame),
("screenshot.rs", ".ResourceBarrier(", 2, Reason::OutOfFrame),
("draw/composite.rs", ".ResourceBarrier(", 2, Reason::Present),
(
"auto_exposure.rs",
".ResourceBarrier(",
6,
Reason::Ungraphed,
),
("cull.rs", ".ResourceBarrier(", 6, Reason::Ungraphed),
("planar.rs", ".ResourceBarrier(", 4, Reason::Ungraphed),
(
"post/upscale/fsr.rs",
".ResourceBarrier(",
2,
Reason::Inline,
),
],
},
];
fn rust_sources(dir: &Path, prefix: &str, out: &mut Vec<(String, PathBuf)>) {
let entries = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read {}: {e}", dir.display()));
for entry in entries {
let path = entry.expect("dir entry").path();
let name = path
.file_name()
.and_then(|n| n.to_str())
.expect("utf-8 file name")
.to_string();
let rel = if prefix.is_empty() {
name.clone()
} else {
format!("{prefix}/{name}")
};
if path.is_dir() {
rust_sources(&path, &rel, out);
} else if name.ends_with(".rs") {
out.push((rel, path));
}
}
}
fn call_sites(source: &str, call: &str) -> usize {
source
.lines()
.map(|line| line.split("//").next().unwrap_or(""))
.map(|code| code.matches(call).count())
.sum()
}
fn scan(audit: &BackendAudit) -> BTreeMap<(String, &'static str), usize> {
let mut files = Vec::new();
rust_sources(Path::new(audit.root), "", &mut files);
let mut found = BTreeMap::new();
for (rel, path) in files {
let source = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {rel}: {e}"));
for &call in audit.calls {
let n = call_sites(&source, call);
if n > 0 {
found.insert((rel.clone(), call), n);
}
}
}
found
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_barrier_call_site_is_classified() {
let mut failures = Vec::new();
for audit in AUDITS {
let found = scan(audit);
let expected: BTreeMap<(String, &str), (usize, Reason)> = audit
.sites
.iter()
.map(|&(path, call, n, reason)| ((path.to_string(), call), (n, reason)))
.collect();
for ((path, call), &n) in &found {
match expected.get(&(path.clone(), *call)) {
None => failures.push(format!(
"{}/{path}: {n} `{call}` site(s) with no entry in the audit table; \
classify them (or move them into the graph executor)",
audit.backend
)),
Some(&(want, _)) if want != n => failures.push(format!(
"{}/{path}: audit table says {want} `{call}` site(s), found {n}; \
update the table",
audit.backend
)),
Some(_) => {}
}
}
for ((path, call), (want, reason)) in &expected {
if !found.contains_key(&(path.clone(), *call)) {
failures.push(format!(
"{}/{path}: audit table says {want} `{call}` site(s) ({reason:?}), \
found none; drop the entry",
audit.backend
));
}
}
}
assert!(failures.is_empty(), "\n{}", failures.join("\n"));
}
#[test]
fn the_scan_ignores_barriers_named_in_comments() {
assert_eq!(
call_sites("// feeds cmd_pipeline_barrier\n", "cmd_pipeline_barrier"),
0
);
assert_eq!(
call_sites(" device.cmd_pipeline_barrier(\n", "cmd_pipeline_barrier"),
1
);
assert_eq!(
call_sites(
" cmd.ResourceBarrier(&[b]); // transition\n",
".ResourceBarrier("
),
1
);
}
#[test]
fn the_scan_reaches_nested_backend_modules() {
let vulkan = scan(&AUDITS[0]);
assert!(
vulkan.keys().any(|(p, _)| p.contains('/')),
"expected nested paths in the vulkan scan, got {:?}",
vulkan.keys().collect::<Vec<_>>()
);
}
}