use std::collections::BTreeMap;
use locum_capture::rdef::{ConstantBuffer, Type};
use crate::describe::Reflection;
use crate::describe::short;
use crate::draw::{Lookup, constants_of, file_of, layout_slots, read_vertices, rows};
use crate::draws::shader_of;
use crate::open::Capture;
use crate::out;
pub struct Filter {
pub name: Vec<String>,
pub draws: Option<(i64, i64)>,
pub stage: Option<String>,
pub distinct: bool,
pub extend: bool,
pub with: Vec<String>,
}
pub const WITH: [&str; 9] = ["vs", "hs", "ds", "gs", "ps", "cs", "stencil", "topology", "file"];
fn draw_columns(c: &Capture, n: i64, with: &[String], reflection: &mut Reflection) -> Vec<String> {
with.iter()
.map(|w| match w.as_str() {
"stencil" | "topology" => {
let column = if w == "stencil" { "stencil_ref" } else { "topology" };
c.row(&format!("SELECT {column} FROM draws WHERE n = ?1"), [n], |r| {
r.get::<_, Option<i64>>(0)
})
.flatten()
.map_or("-".into(), |v| v.to_string())
}
"file" => mesh_file(c, n).unwrap_or_else(|| "-".into()),
stage => match shader_of(c, n, stage) {
Some(h) => reflection.name(c, &h).unwrap_or_else(|| short(&h).to_string()),
None => "-".into(),
},
})
.collect()
}
fn mesh_file(c: &Capture, n: i64) -> Option<String> {
let (call, args, layout): (String, String, Option<i64>) = c.row(
"SELECT call, coalesce(args, ''), layout FROM draws WHERE n = ?1",
[n],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
)?;
let bound = rows(c, n);
let slots = layout_slots(c, layout);
let vbs = read_vertices(&bound, slots.as_deref());
bound
.iter()
.filter(|b| b.kind == "ib")
.chain(
bound
.iter()
.filter(|b| b.kind == "vb" && slots.as_ref().is_none_or(|s| s.contains(&b.slot))),
)
.find_map(|b| file_of(c, b, &call, &args, &vbs))
}
impl Filter {
pub fn new(
name: &str,
draws: Option<(i64, i64)>,
stage: Option<String>,
distinct: bool,
extend: bool,
with: Vec<String>,
) -> Filter {
Filter {
name: name
.split('|')
.map(|p| p.trim().to_lowercase())
.filter(|p| !p.is_empty())
.collect(),
draws,
stage,
distinct,
extend,
with,
}
}
fn matches(&self, name: &str) -> bool {
let name = name.to_lowercase();
self.name.iter().any(|p| name.contains(p))
}
fn may_match(&self, cb: &ConstantBuffer) -> bool {
let mut names = vec![cb.name.to_lowercase()];
fn walk(ty: &Type, names: &mut Vec<String>) {
for m in &ty.members {
names.push(m.name.to_lowercase());
walk(&m.ty, names);
}
}
for v in &cb.variables {
names.push(v.name.to_lowercase());
walk(&v.ty, &mut names);
}
self.name.iter().any(|p| {
p.split(|ch: char| !(ch.is_alphanumeric() || ch == '_'))
.filter(|t| !t.is_empty() && !t.chars().all(|ch| ch.is_ascii_digit()))
.filter(|t| !matches!(*t, "x" | "y" | "z" | "w"))
.all(|t| names.iter().any(|n| n.contains(t)))
})
}
}
struct Found {
draw: i64,
pass: Option<i64>,
stage: String,
slot: u32,
buffer: String,
register: String,
name: String,
value: String,
hex: String,
with: Vec<String>,
}
fn found(c: &Capture, f: &Filter) -> Result<(Vec<Found>, usize), String> {
let (first, last) = f.draws.unwrap_or((i64::MIN, i64::MAX));
let draws: Vec<(i64, Option<i64>)> = c.rows(
"SELECT n, pass FROM draws WHERE n BETWEEN ?1 AND ?2 ORDER BY n",
[first, last],
|r| Ok((r.get(0)?, r.get(1)?)),
);
let mut reflection = Reflection::default();
let lookup = Lookup::new(f.extend, None);
let mut out = Vec::new();
let mut uncaptured = 0;
for (n, pass) in draws {
let mut with: Option<Vec<String>> = None;
for b in rows(c, n).iter().filter(|b| b.kind == "cb") {
if f.stage.as_ref().is_some_and(|s| *s != b.stage) {
continue;
}
let Some(shader) = shader_of(c, n, &b.stage) else {
continue;
};
let Some(cb) = reflection.cb(c, &shader, b.slot, b.space) else {
continue;
};
if !f.may_match(cb) {
continue;
}
let k = constants_of(c, &mut reflection, n, b, false, &lookup)?;
if k.why.is_some() {
uncaptured += 1;
continue;
}
let buffer = k.name.clone().unwrap_or_default();
for w in k.words {
let Some(name) = w.name.filter(|name| f.matches(name)) else {
continue;
};
let with = with
.get_or_insert_with(|| draw_columns(c, n, &f.with, &mut reflection))
.clone();
out.push(Found {
draw: n,
pass,
stage: b.stage.clone(),
slot: b.slot,
buffer: buffer.clone(),
register: w.register,
name,
value: w.text.trim().to_string(),
hex: w.hex,
with,
});
}
}
}
Ok((out, uncaptured))
}
pub fn constants(
captures: &[String],
open: impl Fn(&str) -> Result<Capture, String>,
f: &Filter,
) -> Result<(), String> {
if f.name.is_empty() {
return Err("--name: give a part of a constant's name".into());
}
let several = captures.len() > 1;
let lead = |capture: &str| {
if several {
format!("{capture}\t")
} else {
String::new()
}
};
let head = if several { "capture\t" } else { "" };
let extra: String = f.with.iter().map(|w| format!("\t{w}")).collect();
if f.distinct {
out!("{head}name\tvalue\thex{extra}\tdraws\tfirst\tlast");
} else {
out!("{head}draw\tpass\tstage\tslot\tbuffer\tregister\tname\tvalue\thex{extra}");
}
let mut any = false;
for capture in captures {
let c = open(capture)?;
let (words, uncaptured) = found(&c, f)?;
if uncaptured > 0 {
eprintln!(
"locum-cli: {capture}: {uncaptured} bound buffers declaring a matching name have no contents recorded \
(`info` says why)"
);
}
any |= !words.is_empty();
if f.distinct {
type Values = BTreeMap<(String, String, Vec<String>), Vec<i64>>;
let mut by: BTreeMap<String, Values> = BTreeMap::new();
for w in words {
by.entry(w.name)
.or_default()
.entry((w.value, w.hex, w.with))
.or_default()
.push(w.draw);
}
for (name, values) in by {
for ((value, hex, with), mut draws) in values {
draws.sort_unstable();
draws.dedup();
let with: String = with.iter().map(|w| format!("\t{w}")).collect();
out!(
"{}{name}\t{value}\t{hex}{with}\t{}\t{}\t{}",
lead(capture),
draws.len(),
draws[0],
draws[draws.len() - 1]
);
}
}
} else {
for w in words {
let with: String = w.with.iter().map(|v| format!("\t{v}")).collect();
out!(
"{}{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}{with}",
lead(capture),
w.draw,
w.pass.map_or("-".into(), |p| p.to_string()),
w.stage,
w.slot,
w.buffer,
w.register,
w.name,
w.value,
w.hex
);
}
}
}
if !any {
eprintln!(
"locum-cli: no constant named like that is bound with contents{}",
if f.extend || !f.name.iter().any(|p| p.contains('[')) {
""
} else {
" (an element past an array's declared length is named with --extend)"
}
);
}
Ok(())
}