diff options
| author | s-ol <s+removethis@s-ol.nu> | 2025-12-25 11:49:56 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-12-25 11:49:56 +0000 |
| commit | c9a0bf6afbda1b7be6a98916fde8603f78ce14d0 (patch) | |
| tree | da8bd6de44228d8c8d55538fc29a50a416d6f0a7 /src | |
| parent | basic toposorted SSA (diff) | |
| download | nodetoy-c9a0bf6afbda1b7be6a98916fde8603f78ce14d0.tar.gz nodetoy-c9a0bf6afbda1b7be6a98916fde8603f78ce14d0.zip | |
pretty good GLSL
Diffstat (limited to 'src')
| -rw-r--r-- | src/library.rs | 125 | ||||
| -rw-r--r-- | src/main.rs | 152 | ||||
| -rw-r--r-- | src/preview.rs | 23 |
3 files changed, 235 insertions, 65 deletions
diff --git a/src/library.rs b/src/library.rs index d0b97ef..ed96dea 100644 --- a/src/library.rs +++ b/src/library.rs @@ -1,6 +1,7 @@ use enum_dispatch::enum_dispatch; use itertools::Itertools; use serde::{Deserialize, Serialize}; +use std::fmt; use crate::{ ConcreteNode, Dimension::*, FloatPrecision::*, ScalarType::*, Type, Type::*, TypeSignature, @@ -38,12 +39,13 @@ const GEN_B_TYPES: [Type; 4] = [ ]; /// Helper trait for Nodes with fixed number of inputs and outputs -trait FixedNode { +trait FixedNode: fmt::Debug { const NI: usize; const NO: usize; fn all_signatures(&self) -> Box<dyn Iterator<Item = TypeSignature>>; } + impl<T: FixedNode> ConcreteNode for T { fn inputs(&self) -> usize { Self::NI @@ -64,6 +66,25 @@ impl<T: FixedNode> ConcreteNode for T { .find(|sig| sig.matches_inputs(connected)) .expect("have to have fallback") } + + fn compile( + &self, + signature: TypeSignature, + inputs: Vec<String>, + outputs: Vec<String>, + f: &mut dyn fmt::Write, + ) -> fmt::Result { + for i in 1..signature.outputs.len() { + write!(f, "{} {};\n", signature.outputs[i], outputs[i])?; + } + + let func = format!("{self:?}").to_lowercase(); + let out_typ = &signature.outputs[0]; + let mut outputs = outputs.into_iter(); + let out_name = outputs.next().expect("empty output"); + let params = inputs.into_iter().chain(outputs).join(", "); + write!(f, "{out_typ} {out_name} = {func}({params});\n") + } } /// Arithmetic Operations @@ -288,7 +309,7 @@ impl FixedNode for Mix { } #[enum_dispatch(ConcreteNode)] -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum BuiltinFunction { BinArithmetic(BinArithmetic), Thru1(Thru1), @@ -312,26 +333,79 @@ impl BuiltinFunction { } } +impl fmt::Display for BuiltinFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BuiltinFunction::BinArithmetic(x) => fmt::Debug::fmt(x, f), + BuiltinFunction::Thru1(x) => fmt::Debug::fmt(x, f), + BuiltinFunction::Mod(x) => fmt::Debug::fmt(x, f), + BuiltinFunction::Modf(x) => fmt::Debug::fmt(x, f), + BuiltinFunction::MinMax(x) => fmt::Debug::fmt(x, f), + BuiltinFunction::Clamp(x) => fmt::Debug::fmt(x, f), + BuiltinFunction::Mix(x) => fmt::Debug::fmt(x, f), + } + } +} + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Output; -impl FixedNode for Output { - const NI: usize = 1; - const NO: usize = 0; +impl ConcreteNode for Output { + fn inputs(&self) -> usize { + 1 + } + fn outputs(&self) -> usize { + 0 + } + + fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature> { + self.all_signatures() + .filter(|sig| sig.matches_inputs(connected)) + .collect() + } + + fn signature(&self, connected: &[Option<Type>]) -> TypeSignature { + self.all_signatures() + .find(|sig| sig.matches_inputs(connected)) + .expect("have to have fallback") + } + + fn compile( + &self, + signature: TypeSignature, + inputs: Vec<String>, + _outputs: Vec<String>, + f: &mut dyn fmt::Write, + ) -> fmt::Result { + match signature.inputs[0] { + Vector(_, D3) => write!(f, "gl_FragColor = vec4({}, 1.0);\n", inputs[0]), + Vector(_, D4) => write!(f, "gl_FragColor = {};\n", inputs[0]), + Scalar(_) => write!(f, "gl_FragColor = vec4(vec3({}), 1.0);\n", inputs[0]), + _ => unreachable!("invalid output type"), + } + } +} +impl Output { fn all_signatures(&self) -> Box<dyn Iterator<Item = TypeSignature>> { Box::new( [ TypeSignature::new([Vector(Float(Single), D3)], []), TypeSignature::new([Vector(Float(Single), D4)], []), + TypeSignature::new([Scalar(Float(Single))], []), + TypeSignature::new([Scalar(Float(Double))], []), ] .into_iter(), ) } -} -impl Output { + pub fn all() -> impl Iterator<Item = Self> { std::iter::once(Output) } } +impl fmt::Display for Output { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Output Color") + } +} #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Constant { @@ -339,14 +413,34 @@ pub struct Constant { value: String, } -impl FixedNode for Constant { - const NI: usize = 0; - const NO: usize = 1; - fn all_signatures(&self) -> Box<dyn Iterator<Item = TypeSignature>> { - Box::new(std::iter::once(TypeSignature::new([], [self.typ]))) +impl ConcreteNode for Constant { + fn inputs(&self) -> usize { + 0 + } + fn outputs(&self) -> usize { + 1 + } + + fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature> { + vec![self.signature(connected)] } -} + fn signature(&self, _connected: &[Option<Type>]) -> TypeSignature { + TypeSignature::new([], [self.typ]) + } + + fn compile( + &self, + signature: TypeSignature, + _inputs: Vec<String>, + outputs: Vec<String>, + f: &mut dyn fmt::Write, + ) -> fmt::Result { + let out_typ = &signature.outputs[0]; + let out_name = &outputs[0]; + write!(f, "{out_typ} {out_name} = {};\n", self.value) + } +} impl Constant { pub fn all() -> impl Iterator<Item = Constant> { [ @@ -378,3 +472,8 @@ impl Constant { .into_iter() } } +impl fmt::Display for Constant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Constant {}", self.typ) + } +} diff --git a/src/main.rs b/src/main.rs index 6dafdfd..33428ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,16 @@ #![allow(clippy::use_self)] -use log::*; - use eframe::{App, CreationContext}; use egui::{Color32, Id, Ui}; use egui_snarl::{ InPin, InPinId, NodeId, OutPin, OutPinId, Snarl, ui::{ - NodeLayout, PinInfo, PinPlacement, SnarlStyle, SnarlViewer, SnarlWidget, - get_selected_nodes, + NodeLayout, PinInfo, PinPlacement, SnarlStyle, SnarlViewer, SnarlWidget, get_selected_nodes, }, }; use enum_dispatch::enum_dispatch; +use log::*; +use std::fmt; #[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] enum FloatPrecision { @@ -28,10 +27,16 @@ enum ScalarType { } #[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[repr(u8)] enum Dimension { - D2, - D3, - D4, + D2 = 2, + D3 = 3, + D4 = 4, +} +impl fmt::Display for Dimension { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", *self as u8) + } } #[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] @@ -51,6 +56,46 @@ impl Type { } } +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Scalar(s) => write!( + f, + "{}", + match s { + ScalarType::Float(FloatPrecision::Single) => "float", + ScalarType::Float(FloatPrecision::Double) => "double", + ScalarType::Int => "int", + ScalarType::UInt => "uint", + ScalarType::Bool => "bool", + } + ), + Type::Vector(s, d) => write!( + f, + "{}vec{d}", + match s { + ScalarType::Float(FloatPrecision::Single) => "", + ScalarType::Float(FloatPrecision::Double) => "d", + ScalarType::Int => "i", + ScalarType::UInt => "u", + ScalarType::Bool => "b", + } + ), + Type::Matrix(p, r, c) => { + let pr = match p { + FloatPrecision::Single => "", + FloatPrecision::Double => "d", + }; + if r == c { + write!(f, "{pr}mat{r}") + } else { + write!(f, "{pr}mat{r}{c}") + } + } + } + } +} + /// a single concrete type signature for a function #[derive(PartialEq)] struct TypeSignature { @@ -76,6 +121,14 @@ trait ConcreteNode { fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature>; fn signature(&self, connected: &[Option<Type>]) -> TypeSignature; + + fn compile( + &self, + signature: TypeSignature, + inputs: Vec<String>, + outputs: Vec<String>, + f: &mut dyn fmt::Write, + ) -> fmt::Result; } impl TypeSignature { @@ -102,7 +155,7 @@ const IMAGE_COLOR: Color32 = Color32::from_rgb(0xb0, 0x00, 0xb0); const UNTYPED_COLOR: Color32 = Color32::from_rgb(0xb0, 0xb0, 0xb0); #[enum_dispatch(ConcreteNode)] -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] enum DemoNode { Constant(library::Constant), Builtin(library::BuiltinFunction), @@ -122,13 +175,20 @@ impl DemoNode { } fn compile_output(pin: OutPinId) -> String { - let node_id = pin.node.0; - let out_id = pin.output; - format!("n{node_id}_o{out_id}") + let node_id = pin.node.0; + let out_id = pin.output; + format!("n{node_id}_o{out_id}") } - fn get_inputs(&self, node: NodeId, snarl: &Snarl<DemoNode>) -> String { - (0..self.inputs()) + pub fn compile_node( + &self, + node: NodeId, + snarl: &Snarl<DemoNode>, + f: &mut dyn fmt::Write, + ) -> fmt::Result { + let signature = self.get_node_signature(node, snarl); + + let inputs: Vec<String> = (0..self.inputs()) .map( |input| match &*snarl.in_pin(InPinId { node, input }).remotes { [] => "?".to_owned(), @@ -136,24 +196,12 @@ impl DemoNode { _ => unreachable!("cannot connect to multiple inputs"), }, ) - .collect::<Vec<_>>() - .join(", ") - } + .collect(); + let outputs: Vec<String> = (0..self.outputs()) + .map(|output| DemoNode::compile_output(OutPinId { node, output })) + .collect(); - pub fn compile(&self, node: NodeId, snarl: &Snarl<DemoNode>) -> String { - let sig = self.get_node_signature(node, snarl); - let typ = if sig.outputs.is_empty() { - "".to_owned() - } else { - let output = sig.outputs[0]; - format!("{output:?}") - }; - - let node_id = node.0; - let name = format!("{self:?}"); - let inputs = self.get_inputs(node, snarl); - - format!("{typ} n{node_id}_o0 = {name}({inputs});\n") + snarl[node].compile(signature, inputs, outputs, f) } pub fn get_node_signature(&self, node: NodeId, snarl: &Snarl<DemoNode>) -> TypeSignature { @@ -168,6 +216,15 @@ impl DemoNode { .chain(library::BuiltinFunction::all().map(DemoNode::from)) } } +impl fmt::Display for DemoNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DemoNode::Constant(x) => fmt::Display::fmt(x, f), + DemoNode::Builtin(x) => fmt::Display::fmt(x, f), + DemoNode::Output(x) => fmt::Display::fmt(x, f), + } + } +} struct DemoViewer; @@ -183,6 +240,22 @@ impl DemoViewer { let sig = &node.get_node_signature(pin.node, snarl); sig.outputs[pin.output] } + + pub fn compile(snarl: &Snarl<DemoNode>, f: &mut dyn fmt::Write) -> fmt::Result { + let mut order = topological_sort::TopologicalSort::<NodeId>::new(); + for (out, inp) in snarl.wires() { + order.add_dependency(out.node, inp.node); + } + + write!(f, "in vec2 _UV;\n")?; + write!(f, "void main() {{\n")?; + while let Some(id) = order.pop() { + snarl[id].compile_node(id, snarl, f)?; + } + write!(f, "}}\n")?; + + Ok(()) + } } impl SnarlViewer<DemoNode> for DemoViewer { @@ -205,7 +278,7 @@ impl SnarlViewer<DemoNode> for DemoViewer { } fn title(&mut self, node: &DemoNode) -> String { - format!("{node:?}") + format!("{node}") } fn inputs(&mut self, node: &DemoNode) -> usize { @@ -234,7 +307,7 @@ impl SnarlViewer<DemoNode> for DemoViewer { #[allow(refining_impl_trait)] fn show_output(&mut self, pin: &OutPin, ui: &mut Ui, snarl: &mut Snarl<DemoNode>) -> PinInfo { let typ = DemoViewer::get_out_type(pin.id, snarl); - ui.label(format!("{typ:?}")); + ui.label(format!("{typ}")); PinInfo::circle().with_fill(match typ { Type::Scalar(_) => NUMBER_COLOR, Type::Vector(_, Dimension::D2) => IMAGE_COLOR, @@ -252,7 +325,7 @@ impl SnarlViewer<DemoNode> for DemoViewer { ui.label("Add node"); for node in DemoNode::all() { - if ui.button(format!("{node:?}")).clicked() { + if ui.button(format!("{node}")).clicked() { snarl.insert_node(pos, node); ui.close(); } @@ -379,7 +452,7 @@ impl App for DemoApp { for (id, node) in selected { ui.horizontal(|ui| { ui.label(format!("{id:?}")); - ui.label(format!("{node:?}")); + ui.label(format!("{node}")); ui.add_space(ui.spacing().item_spacing.x); if ui.button("Remove").clicked() { remove = Some(id); @@ -396,7 +469,16 @@ impl App for DemoApp { egui::SidePanel::right("preview").show(ctx, |ui| { egui::ScrollArea::vertical().show(ui, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| { - self.preview.custom_painting(ui, &self.snarl); + let shader = if ui.button("compile").clicked() { + let mut buf = String::new(); + DemoViewer::compile(&self.snarl, &mut buf).expect("compilation"); + info!("{}", buf); + Some(buf) + } else { + None + }; + + self.preview.custom_painting(ui, shader); }); }); }); diff --git a/src/preview.rs b/src/preview.rs index 94bf128..812a32b 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -7,9 +7,6 @@ use eframe::{ egui_wgpu::wgpu::util::DeviceExt as _, egui_wgpu::{self, wgpu}, }; -use egui_snarl::{ - Snarl, NodeId, -}; pub struct Custom3d { angle: f32, @@ -170,6 +167,7 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> { // which can be used to issue draw commands. struct CustomTriangleCallback { angle: f32, + frag_shader: Option<String>, } impl egui_wgpu::CallbackTrait for CustomTriangleCallback { @@ -198,27 +196,18 @@ impl egui_wgpu::CallbackTrait for CustomTriangleCallback { } impl Custom3d { - pub fn custom_painting(&mut self, ui: &mut egui::Ui, snarl: &Snarl<crate::DemoNode>) { + pub fn custom_painting(&mut self, ui: &mut egui::Ui, frag_shader: Option<String>) { let (rect, response) = ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); self.angle += response.drag_motion().x * 0.01; ui.painter().add(egui_wgpu::Callback::new_paint_callback( rect, - CustomTriangleCallback { angle: self.angle }, + CustomTriangleCallback { + angle: self.angle, + frag_shader, + }, )); - - if ui.button("compile").clicked() { - let mut order = topological_sort::TopologicalSort::<NodeId>::new(); - for (out, inp) in snarl.wires() { - order.add_dependency(out.node, inp.node); - } - - // let buf = BufWriter::new(Vec::new()); - while let Some(id) = order.pop() { - info!("{}", snarl[id].compile(id, snarl)); - } - } } } |
