use crate::types::shader::ShaderUniformValue;
use std::collections::{BTreeMap, HashSet};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SliderScale {
Linear,
Log,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AngleUnit {
Degrees,
Radians,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ShaderControlKind {
Slider {
min: f32,
max: f32,
step: f32,
scale: SliderScale,
label: Option<String>,
},
Checkbox { label: Option<String> },
Color { alpha: bool, label: Option<String> },
Int {
min: i32,
max: i32,
step: i32,
label: Option<String>,
},
Select {
options: Vec<String>,
label: Option<String>,
},
Vec2 {
min: f32,
max: f32,
step: f32,
label: Option<String>,
},
Point { label: Option<String> },
Range {
min: f32,
max: f32,
step: f32,
label: Option<String>,
},
Angle {
unit: AngleUnit,
label: Option<String>,
},
Channel {
options: Vec<i32>,
label: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShaderControl {
pub name: String,
pub kind: ShaderControlKind,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShaderControlWarning {
pub line: usize,
pub message: String,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ShaderControlParseResult {
pub controls: Vec<ShaderControl>,
pub warnings: Vec<ShaderControlWarning>,
pub groups: BTreeMap<String, String>,
}
const MAX_SHADER_FLOAT_CONTROLS: usize = 16;
const MAX_SHADER_BOOL_CONTROLS: usize = 16;
const MAX_SHADER_COLOR_CONTROLS: usize = 16;
const MAX_SHADER_INT_CONTROLS: usize = 16;
const MAX_SHADER_VEC2_CONTROLS: usize = 16;
mod float_kinds;
mod int_kinds;
mod parse_helpers;
mod simple_kinds;
mod vec2_kinds;
#[cfg(test)]
mod tests;
use parse_helpers::{
ControlDirective, check_and_push_capacity_warning, parse_key_values, parse_quoted_label,
parse_uniform_declaration, push_warning, tokenize_control_directive,
};
#[derive(Default)]
struct CapacityCounters {
float: usize,
boolean: usize,
color: usize,
int: usize,
vec2: usize,
}
impl CapacityCounters {
fn claim(
&mut self,
kind: &ShaderControlKind,
warnings: &mut Vec<ShaderControlWarning>,
line: usize,
uniform_name: &str,
) -> bool {
let (type_label, limit, count) = match kind {
ShaderControlKind::Slider { .. } | ShaderControlKind::Angle { .. } => {
("float", MAX_SHADER_FLOAT_CONTROLS, &mut self.float)
}
ShaderControlKind::Checkbox { .. } => {
("checkbox", MAX_SHADER_BOOL_CONTROLS, &mut self.boolean)
}
ShaderControlKind::Color { .. } => {
("color", MAX_SHADER_COLOR_CONTROLS, &mut self.color)
}
ShaderControlKind::Int { .. }
| ShaderControlKind::Select { .. }
| ShaderControlKind::Channel { .. } => ("int", MAX_SHADER_INT_CONTROLS, &mut self.int),
ShaderControlKind::Vec2 { .. }
| ShaderControlKind::Point { .. }
| ShaderControlKind::Range { .. } => ("vec2", MAX_SHADER_VEC2_CONTROLS, &mut self.vec2),
};
if check_and_push_capacity_warning(warnings, line, type_label, limit, *count, uniform_name)
{
return false;
}
*count += 1;
true
}
}
fn parse_control_kind(
directive: &ControlDirective<'_>,
warnings: &mut Vec<ShaderControlWarning>,
) -> Option<ShaderControlKind> {
match directive.control_type {
"slider" => float_kinds::parse_slider(directive, warnings),
"checkbox" => simple_kinds::parse_checkbox(directive, warnings),
"color" => simple_kinds::parse_color(directive, warnings),
"int" => int_kinds::parse_int(directive, warnings),
"select" => int_kinds::parse_select(directive, warnings),
"vec2" => vec2_kinds::parse_vec2(directive, warnings),
"point" => vec2_kinds::parse_point(directive, warnings),
"range" => vec2_kinds::parse_range(directive, warnings),
"angle" => float_kinds::parse_angle(directive, warnings),
"channel" => int_kinds::parse_channel(directive, warnings),
other => {
directive.warn(warnings, format!("Unsupported control type `{}`", other));
None
}
}
}
pub fn parse_shader_controls(source: &str) -> ShaderControlParseResult {
let lines: Vec<&str> = source.lines().collect();
let mut controls = Vec::new();
let mut warnings = Vec::new();
let mut groups = BTreeMap::new();
let mut seen = HashSet::new();
let mut capacity = CapacityCounters::default();
for (index, line) in lines.iter().enumerate() {
let trimmed = line.trim();
let Some(rest) = trimmed.strip_prefix("// control ") else {
continue;
};
let line_number = index + 1;
let (tokens, tokenization_warnings) = tokenize_control_directive(rest);
let Some(control_type) = tokens.first().map(String::as_str) else {
push_warning(
&mut warnings,
line_number,
"Control comment is missing a control type".to_string(),
);
continue;
};
let Some(next_line) = lines.get(index + 1) else {
push_warning(
&mut warnings,
line_number,
"Control comment must be immediately followed by a uniform declaration".to_string(),
);
continue;
};
let Some((uniform_type, uniform_name)) = parse_uniform_declaration(next_line) else {
push_warning(
&mut warnings,
line_number,
"Control comment must be immediately followed by a uniform declaration".to_string(),
);
continue;
};
let (key_values, mut malformed_tokens) = parse_key_values(&tokens[1..]);
malformed_tokens.extend(tokenization_warnings);
let directive = ControlDirective {
line: line_number,
control_type,
uniform_type,
uniform_name,
key_values: &key_values,
malformed_tokens: &malformed_tokens,
};
let Some(kind) = parse_control_kind(&directive, &mut warnings) else {
continue;
};
if !seen.insert(uniform_name.to_string()) {
push_warning(
&mut warnings,
line_number,
format!("Duplicate control for uniform `{}` ignored", uniform_name),
);
continue;
}
if !capacity.claim(&kind, &mut warnings, line_number, uniform_name) {
continue;
}
if let Some(group) = parse_quoted_label(
&mut warnings,
line_number,
"Control group",
uniform_name,
key_values.get("group"),
) {
groups.insert(uniform_name.to_string(), group);
}
controls.push(ShaderControl {
name: uniform_name.to_string(),
kind,
});
}
ShaderControlParseResult {
controls,
warnings,
groups,
}
}
pub fn fallback_value_for_control(control: &ShaderControl) -> ShaderUniformValue {
match &control.kind {
ShaderControlKind::Slider { min, .. } => ShaderUniformValue::Float(*min),
ShaderControlKind::Checkbox { .. } => ShaderUniformValue::Bool(false),
ShaderControlKind::Color { .. } => {
ShaderUniformValue::Color(crate::types::shader::ShaderColorValue([1.0, 1.0, 1.0, 1.0]))
}
ShaderControlKind::Int { min, .. } => ShaderUniformValue::Int(*min),
ShaderControlKind::Select { .. } => ShaderUniformValue::Int(0),
ShaderControlKind::Vec2 { min, .. } => ShaderUniformValue::Vec2([*min, *min]),
ShaderControlKind::Point { .. } => ShaderUniformValue::Vec2([0.5, 0.5]),
ShaderControlKind::Range { min, max, .. } => ShaderUniformValue::Vec2([*min, *max]),
ShaderControlKind::Angle { .. } => ShaderUniformValue::Float(0.0),
ShaderControlKind::Channel { options, .. } => {
ShaderUniformValue::Int(options.first().copied().unwrap_or(0))
}
}
}