aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2025-12-25 11:49:56 +0000
committers-ol <s+removethis@s-ol.nu>2025-12-25 11:49:56 +0000
commitc9a0bf6afbda1b7be6a98916fde8603f78ce14d0 (patch)
treeda8bd6de44228d8c8d55538fc29a50a416d6f0a7 /src
parentbasic toposorted SSA (diff)
downloadnodetoy-c9a0bf6afbda1b7be6a98916fde8603f78ce14d0.tar.gz
nodetoy-c9a0bf6afbda1b7be6a98916fde8603f78ce14d0.zip
pretty good GLSL
Diffstat (limited to 'src')
-rw-r--r--src/library.rs125
-rw-r--r--src/main.rs152
-rw-r--r--src/preview.rs23
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));
- }
- }
}
}