use super::{Instruction, MAX_VISUAL_INSTRUCTIONS, Opcode, ValueKind, VisualProgram};
use std::fmt::Write as _;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum VisualPipeline {
Specialized,
Interpreter,
Pending,
Failed,
}
impl VisualPipeline {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Specialized => "specialized",
Self::Interpreter => "typed-bytecode-interpreter",
Self::Pending => "specialized-pending",
Self::Failed => "failed",
}
}
#[must_use]
pub const fn is_interpreter(self) -> bool {
!matches!(self, Self::Specialized)
}
#[must_use]
pub const fn default_reason(self) -> &'static str {
match self {
Self::Specialized => "emitted-straight-line",
Self::Interpreter => "program-not-lowerable",
Self::Pending => "compiling",
Self::Failed => "compile-failed",
}
}
#[must_use]
pub fn report(self, key: &str, reason: &str) -> String {
format!(
"pipeline: {}\ncache-key: {key}\nreason: {reason}",
self.label()
)
}
}
#[must_use]
pub fn visual_pipeline(program: &VisualProgram) -> (VisualPipeline, String) {
match unsupported(program) {
None => (
VisualPipeline::Specialized,
VisualPipeline::Specialized.default_reason().to_owned(),
),
Some(error) => (VisualPipeline::Interpreter, error.reason()),
}
}
#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
pub enum VisualEmitError {
#[error("visual instruction {index} uses unsupported opcode {name} ({opcode})")]
UnsupportedOpcode {
index: usize,
opcode: u32,
name: &'static str,
},
#[error("visual program with {count} instructions exceeds the portable bound")]
InstructionLimit {
count: usize,
},
}
impl VisualEmitError {
#[must_use]
pub fn reason(&self) -> String {
match self {
Self::UnsupportedOpcode { index, name, .. } => {
format!("unsupported-opcode:{name}@{index}")
}
Self::InstructionLimit { count } => format!("instruction-limit:{count}"),
}
}
}
#[must_use]
pub const fn opcode_name(opcode: u32) -> &'static str {
match opcode {
0 => "constant",
1 => "input",
2 => "property",
3 => "parameter",
4 => "add",
5 => "subtract",
6 => "multiply",
7 => "safe-divide",
8 => "abs",
9 => "minimum",
10 => "maximum",
11 => "clamp",
12 => "step",
13 => "smooth-step",
14 => "sine",
15 => "mix",
16 => "less",
17 => "greater",
18 => "and",
19 => "or",
20 => "not",
21 => "select",
22 => "dot",
23 => "normalize",
24 => "state",
_ => "unknown",
}
}
#[must_use]
pub const fn is_supported(opcode: u32) -> bool {
opcode <= Opcode::State as u32
&& opcode != Opcode::Property as u32
&& opcode != Opcode::State as u32
}
#[must_use]
pub fn is_specializable(program: &VisualProgram) -> bool {
unsupported(program).is_none()
}
#[must_use]
pub fn unsupported(program: &VisualProgram) -> Option<VisualEmitError> {
if program.instructions.len() > MAX_VISUAL_INSTRUCTIONS {
return Some(VisualEmitError::InstructionLimit {
count: program.instructions.len(),
});
}
program
.instructions
.iter()
.enumerate()
.find(|(_, instruction)| !is_supported(instruction.opcode()))
.map(|(index, instruction)| VisualEmitError::UnsupportedOpcode {
index,
opcode: instruction.opcode(),
name: opcode_name(instruction.opcode()),
})
}
pub fn emit_resolve(program: &VisualProgram) -> Result<String, VisualEmitError> {
let mut source = String::with_capacity(128 + program.instructions.len() * 112);
source.push_str(LADDER_HEAD);
for (index, instruction) in program.instructions.iter().enumerate() {
emit_instruction(&mut source, index, instruction)?;
}
source.push_str(LADDER_TAIL);
Ok(source)
}
const LADDER_HEAD: &str = "\
fn visual_resolve(
inputs: VisualEvaluationInputs,
fallback: VisualFragmentResult,
) -> VisualFragmentResult {
if !VISUAL_FRAGMENT_ENABLED || visual_config.counts.z == 0u {
return fallback;
}
var registers: array<vec4f, 64>;
";
const LADDER_TAIL: &str = " return visual_resolve_registers(®isters, fallback);\n}\n";
fn emit_instruction(
source: &mut String,
index: usize,
instruction: &Instruction,
) -> Result<(), VisualEmitError> {
let opcode = instruction.opcode();
if !is_supported(opcode) {
return Err(VisualEmitError::UnsupportedOpcode {
index,
opcode,
name: opcode_name(opcode),
});
}
let operands = instruction.operands();
let operand = |position: usize| format!("registers[{}]", operands[position]);
let (left, right) = (operand(0), operand(1));
let third = operand(2);
let value = match opcode {
0 => {
let data = instruction.data();
format!(
"vec4f({}, {}, {}, {})",
float(data[0]),
float(data[1]),
float(data[2]),
float(data[3])
)
}
1 => format!("visual_input({}u, inputs)", slot(instruction)),
3 => format!("visual_parameter({}u)", slot(instruction)),
4..=6 => componentwise(instruction, &left, &right, opcode),
7 => format!(
"vec4f(select({left}.x / {right}.x, 0.0, abs({right}.x) <= 1e-8), 0.0, 0.0, 0.0)"
),
8 => format!("vec4f(abs({left}.x), 0.0, 0.0, 0.0)"),
9 => format!("vec4f(visual_ordered_minimum({left}.x, {right}.x), 0.0, 0.0, 0.0)"),
10 => format!("vec4f(visual_ordered_maximum({left}.x, {right}.x), 0.0, 0.0, 0.0)"),
11 => format!("vec4f(visual_ordered_clamp({left}.x, {right}.x, {third}.x), 0.0, 0.0, 0.0)"),
12 => format!("vec4f(visual_ordered_step({left}.x, {right}.x), 0.0, 0.0, 0.0)"),
13 => format!(
concat!(
"vec4f(select(0.0, smoothstep({left}.x, {right}.x, {third}.x), ",
"{left}.x == {left}.x && {right}.x == {right}.x && {left}.x < {right}.x), ",
"0.0, 0.0, 0.0)"
),
left = left,
right = right,
third = third
),
14 => format!("vec4f(sin({left}.x), 0.0, 0.0, 0.0)"),
15 => format!("mix({left}, {right}, vec4f({third}.x))"),
16 => format!("vec4f(select(0.0, 1.0, {left}.x < {right}.x), 0.0, 0.0, 0.0)"),
17 => format!("vec4f(select(0.0, 1.0, {left}.x > {right}.x), 0.0, 0.0, 0.0)"),
18 => format!(
"vec4f(select(0.0, 1.0, visual_truth({left}) && visual_truth({right})), 0.0, 0.0, 0.0)"
),
19 => format!(
"vec4f(select(0.0, 1.0, visual_truth({left}) || visual_truth({right})), 0.0, 0.0, 0.0)"
),
20 => format!("vec4f(select(0.0, 1.0, !visual_truth({left})), 0.0, 0.0, 0.0)"),
21 => format!("select({third}, {right}, visual_truth({left}))"),
22 => format!("vec4f(dot({left}.xyz, {right}.xyz), 0.0, 0.0, 0.0)"),
23 => {
let _ = writeln!(
source,
" let length_squared_{index} = dot({left}.xyz, {left}.xyz);"
);
format!(
concat!(
"vec4f(select({left}.xyz * inverseSqrt(length_squared_{index}), ",
"vec3f(0.0), length_squared_{index} <= 1e-16 ",
"|| length_squared_{index} != length_squared_{index} ",
"|| abs(length_squared_{index}) > 3.402823466e+38), 0.0)"
),
left = left,
index = index
)
}
_ => {
return Err(VisualEmitError::UnsupportedOpcode {
index,
opcode,
name: opcode_name(opcode),
});
}
};
let _ = writeln!(source, " registers[{index}] = {value};");
Ok(())
}
fn componentwise(instruction: &Instruction, left: &str, right: &str, opcode: u32) -> String {
let operator = match opcode {
4 => "+",
5 => "-",
_ => "*",
};
match instruction.kind() {
ValueKind::Scalar => format!("vec4f({left}.x {operator} {right}.x, 0.0, 0.0, 0.0)"),
ValueKind::Color | ValueKind::Bool => {
format!(
"visual_componentwise({}u, {left}, {right}, {opcode}u)",
instruction.kind() as u32
)
}
ValueKind::Vector if opcode == 6 => format!("vec4f({left}.xyz * {right}.x, 0.0)"),
ValueKind::Vector => format!(
concat!(
"vec4f({left}.x {operator} {right}.x, {left}.y {operator} {right}.y, ",
"{left}.z {operator} {right}.z, 0.0)"
),
left = left,
right = right,
operator = operator
),
}
}
fn slot(instruction: &Instruction) -> usize {
super::numeric::decode_slot(instruction.data()[0])
}
fn float(value: f32) -> String {
format!("{value:e}")
}