diff --git a/Cargo.lock b/Cargo.lock index 3a7a5979..0d7d7b79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,12 +331,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - [[package]] name = "bitflags" version = "1.3.2" @@ -722,30 +716,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset 0.8.0", - "scopeguard", -] - [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -1041,12 +1011,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - [[package]] name = "embed_plist" version = "1.2.2" @@ -1096,22 +1060,6 @@ dependencies = [ "libc", ] -[[package]] -name = "exr" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8af5ef47e2ed89d23d0ecbc1b681b30390069de70260937877514377fc24feb" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide", - "smallvec", - "threadpool", - "zune-inflate", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -1137,7 +1085,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" dependencies = [ - "memoffset 0.6.5", + "memoffset", "rustc_version 0.3.3", ] @@ -1169,19 +1117,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin 0.9.5", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1453,20 +1388,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "gif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" -dependencies = [ - "color_quant", - "weezl", ] [[package]] @@ -2136,14 +2059,9 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", - "exr", - "gif", - "jpeg-decoder", "num-rational", "num-traits", "png", - "scoped_threadpool", - "tiff", ] [[package]] @@ -2262,15 +2180,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jpeg-decoder" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" -dependencies = [ - "rayon", -] - [[package]] name = "js-sys" version = "0.3.61" @@ -2329,12 +2238,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - [[package]] name = "levenberg-marquardt" version = "0.12.0" @@ -2529,15 +2432,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - [[package]] name = "metal" version = "0.24.0" @@ -2644,15 +2538,6 @@ dependencies = [ "syn", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom 0.2.8", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -3443,28 +3328,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" -[[package]] -name = "rayon" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -3746,12 +3609,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - [[package]] name = "scopeguard" version = "1.1.0" @@ -4024,12 +3881,6 @@ dependencies = [ "wide", ] -[[package]] -name = "simd-adler32" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5df39617d7c8558154693a1bb8157a4aab8179209540cc0b10e5dc24e0b18" - [[package]] name = "siphasher" version = "0.3.10" @@ -4693,26 +4544,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "tiff" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - [[package]] name = "time" version = "0.1.45" @@ -5455,12 +5286,6 @@ dependencies = [ "windows-metadata", ] -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - [[package]] name = "wgpu" version = "0.14.2" @@ -5932,12 +5757,3 @@ name = "xxhash-rust" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" - -[[package]] -name = "zune-inflate" -version = "0.2.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589245df6230839c305984dcc0a8385cc72af1fd223f360ffd5d65efa4216d40" -dependencies = [ - "simd-adler32", -] diff --git a/Cargo.toml b/Cargo.toml index 7dbc80f2..5642ac63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,19 +24,26 @@ members = [ resolver = "2" -exclude = [ - "node-graph/gpu-compiler", -] +exclude = ["node-graph/gpu-compiler"] [workspace.dependencies] -specta = { git = "https://github.com/oscartbeaumont/rspc", rev = "9725ddbfe40183debc055b88c37910eb6f818eae", features = ["glam"] } +specta = { git = "https://github.com/oscartbeaumont/rspc", rev = "9725ddbfe40183debc055b88c37910eb6f818eae", features = [ + "glam", +] } xxhash-rust = { version = "0.8.4", features = ["xxh3"] } -[profile.release.package.graphite-wasm] -opt-level = 3 + +[profile.dev.package.graphite-editor] +opt-level = 1 + +[profile.dev.package.graphene-core] +opt-level = 1 + +[profile.dev.package.graphene-std] +opt-level = 1 [profile.dev.package.graphite-wasm] -opt-level = 3 +opt-level = 1 [profile.dev.package.autoquant] opt-level = 3 diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 04684c53..3360c987 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -12,7 +12,10 @@ license = "Apache-2.0" [features] gpu = ["interpreted-executor/gpu", "graphene-std/gpu", "graphene-core/gpu"] -quantization = ["graphene-std/quantization", "interpreted-executor/quantization"] +quantization = [ + "graphene-std/quantization", + "interpreted-executor/quantization", +] [dependencies] log = "0.4" @@ -22,7 +25,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } graphite-proc-macros = { path = "../proc-macros" } bezier-rs = { path = "../libraries/bezier-rs" } -glam = { version="0.22", features = ["serde"] } +glam = { version = "0.22", features = ["serde"] } kurbo = { git = "https://github.com/linebender/kurbo.git", features = [ "serde", ] } @@ -32,7 +35,10 @@ once_cell = "1.13.0" # Remove when `core::cell::OnceCell` is stabilized ( Vec { DocumentNodeType { name: "Image", category: "Ignore", - identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), + identifier: NodeImplementation::DocumentNode(NodeNetwork { + inputs: vec![0], + outputs: vec![NodeOutput::new(2, 0)], + nodes: [ + DocumentNode { + name: "Downscale".to_string(), + inputs: vec![NodeInput::Network(concrete!(ImageFrame))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::DownscaleNode")), + metadata: Default::default(), + }, + DocumentNode { + name: "Cache".to_string(), + inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")), + metadata: Default::default(), + }, + DocumentNode { + name: "Clone".to_string(), + inputs: vec![NodeInput::node(1, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::CloneNode<_>")), + metadata: Default::default(), + }, + ] + .into_iter() + .enumerate() + .map(|(id, node)| (id as NodeId, node)) + .collect(), + ..Default::default() + }), inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), false)], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"), @@ -422,7 +450,7 @@ fn static_nodes() -> Vec { 0, DocumentNode { name: "CacheNode".to_string(), - inputs: vec![NodeInput::Network(concrete!(Image))], + inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(concrete!(ImageFrame))], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")), metadata: Default::default(), }, diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 9c4f1811..107c5c85 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -81,6 +81,7 @@ impl NodeGraphExecutor { inner_network.outputs[0] = NodeOutput::new(*node_id, *output_index); break 'outer; } + NodeInput::ShortCircut(_) => (), } } @@ -92,7 +93,7 @@ impl NodeGraphExecutor { use image::{ImageBuffer, Rgba}; use std::io::Cursor; - let (result_bytes, width, height) = image.as_flat_u8(); + let (result_bytes, width, height) = image.into_flat_u8(); let mut output: ImageBuffer, _> = image::ImageBuffer::from_raw(width, height, result_bytes).ok_or_else(|| "Invalid image size".to_string())?; if let Some(size) = resize { diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 456bd7cd..9e888a70 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -329,10 +329,9 @@ mod image { use alloc::vec::Vec; use core::hash::{Hash, Hasher}; use dyn_any::{DynAny, StaticType}; - use glam::DAffine2; - use glam::DVec2; + use glam::{DAffine2, DVec2}; - #[derive(Clone, Debug, PartialEq, DynAny, Default, specta::Type, Hash)] + #[derive(Clone, Debug, PartialEq, DynAny, Default, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Image { pub width: u32, @@ -340,6 +339,17 @@ mod image { pub data: Vec, } + impl Hash for Image { + fn hash(&self, state: &mut H) { + const HASH_SAMPLES: usize = 1000; + self.width.hash(state); + self.height.hash(state); + for i in 0..HASH_SAMPLES.min(self.data.len()) { + self.data[i * self.data.len() / HASH_SAMPLES].hash(state); + } + } + } + impl Image { pub const fn empty() -> Self { Self { @@ -362,7 +372,7 @@ mod image { } /// Flattens each channel cast to a u8 - pub fn as_flat_u8(self) -> (Vec, u32, u32) { + pub fn into_flat_u8(self) -> (Vec, u32, u32) { let Image { width, height, data } = self; let result_bytes = data.into_iter().flat_map(|color| color.to_rgba8()).collect(); @@ -431,11 +441,12 @@ mod image { &mut self.image.data[y * (self.image.width as usize) + x] } - pub fn sample(&self, x: f64, y: f64) -> Color { - let x = x.clamp(0.0, self.image.width as f64 - 1.0) as usize; - let y = y.clamp(0.0, self.image.height as f64 - 1.0) as usize; + /// Clamps the provided point to (0, 0) (ImageSize) and returns the closest pixel + pub fn sample(&self, position: DVec2) -> Color { + let x = position.x.clamp(0., self.image.width as f64 - 1.) as usize; + let y = position.y.clamp(0., self.image.height as f64 - 1.) as usize; - self.image.data[y * (self.image.width as usize) + x] + self.image.data[x + y * self.image.width as usize] } } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 3efdd0a8..1e0f0136 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -69,6 +69,7 @@ impl DocumentNode { (ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![])) } NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])), + NodeInput::ShortCircut(ty) => (ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![])), }; assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter"); assert!( @@ -121,12 +122,52 @@ impl DocumentNode { } } +/// Represents the possible inputs to a node. +/// # ShortCircuting +/// In Graphite nodes are functions and by default, these are composed into a single function +/// by inserting Compose nodes. +/// +/// +/// +/// +/// ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +/// │ │◄──────────────┤ │◄───────────────┤ │ +/// │ A │ │ B │ │ C │ +/// │ ├──────────────►│ ├───────────────►│ │ +/// └─────────────────┘ └──────────────────┘ └──────────────────┘ +/// +/// +/// +/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`) +/// But sometimes we might want to have a little more control over the order of execution. +/// This is why we allow nodes to opt out of the input forwarding by consuming the input directly. +/// +/// +/// +/// ┌─────────────────────┐ ┌─────────────┐ +/// │ │◄───────────────┤ │ +/// │ Cache Node │ │ C │ +/// │ ├───────────────►│ │ +/// ┌──────────────────┐ ├─────────────────────┤ └─────────────┘ +/// │ │◄──────────────┤ │ +/// │ A │ │ * Cached Node │ +/// │ ├──────────────►│ │ +/// └──────────────────┘ └─────────────────────┘ +/// +/// +/// +/// +/// In this case the Cache node actually consumes it's input and then manually forwards it to it's parameter +/// Node. This is necessary because the Cache Node needs to short-circut the actual node evaluation #[derive(Debug, Clone, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum NodeInput { Node { node_id: NodeId, output_index: usize, lambda: bool }, Value { tagged_value: crate::document::value::TaggedValue, exposed: bool }, Network(Type), + // A short circuting input represents an input that is not resolved through function composition but + // actually consuming the provided input instead of passing it to its predecessor + ShortCircut(Type), } impl NodeInput { @@ -153,6 +194,7 @@ impl NodeInput { NodeInput::Node { .. } => true, NodeInput::Value { exposed, .. } => *exposed, NodeInput::Network(_) => false, + NodeInput::ShortCircut(_) => false, } } pub fn ty(&self) -> Type { @@ -160,6 +202,7 @@ impl NodeInput { NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"), NodeInput::Value { tagged_value, .. } => tagged_value.ty(), NodeInput::Network(ty) => ty.clone(), + NodeInput::ShortCircut(ty) => ty.clone(), } } } @@ -397,6 +440,7 @@ impl NodeNetwork { self.inputs[index] = *network_input; } } + NodeInput::ShortCircut(_) => (), } } node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()); diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 9bbeb071..df375d6b 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -44,6 +44,7 @@ impl core::fmt::Display for ProtoNetwork { match &node.input { ProtoNodeInput::None => f.write_str("None")?, ProtoNodeInput::Network(ty) => f.write_fmt(format_args!("Network (type = {:?})", ty))?, + ProtoNodeInput::ShortCircut(ty) => f.write_fmt(format_args!("Lambda (type = {:?})", ty))?, ProtoNodeInput::Node(_, _) => f.write_str("Node")?, } f.write_str("\n")?; @@ -116,11 +117,19 @@ pub struct ProtoNode { pub identifier: NodeIdentifier, } +/// A ProtoNodeInput represents the input of a node in a ProtoNetwork. +/// For documentation on the meaning of the variants, see the documentation of the `NodeInput` enum +/// in the `document` module #[derive(Debug, PartialEq, Eq, Clone)] pub enum ProtoNodeInput { None, Network(Type), - // the bool indicates whether to treat the node as lambda node + /// A ShortCircut input represents an input that is not resolved through function composition but + /// actually consuming the provided input instead of passing it to its predecessor + ShortCircut(Type), + /// the bool indicates whether to treat the node as lambda node. + /// When treating it as a lambda, only the node that is connected itself is fed as input. + /// Otherwise, the the entire network of which the node is the output is fed as input. Node(NodeId, bool), } @@ -142,6 +151,10 @@ impl ProtoNode { self.construction_args.hash(&mut hasher); match self.input { ProtoNodeInput::None => "none".hash(&mut hasher), + ProtoNodeInput::ShortCircut(ref ty) => { + "lambda".hash(&mut hasher); + ty.hash(&mut hasher); + } ProtoNodeInput::Network(ref ty) => { "network".hash(&mut hasher); ty.hash(&mut hasher); @@ -422,6 +435,7 @@ impl TypingContext { // Get the node input type from the proto node declaration let input = match node.input { ProtoNodeInput::None => concrete!(()), + ProtoNodeInput::ShortCircut(ref ty) => ty.clone(), ProtoNodeInput::Network(ref ty) => ty.clone(), ProtoNodeInput::Node(id, _) => { let input = self diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index f973f6d3..30a3e4aa 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -34,7 +34,7 @@ once_cell = {version= "1.10", optional = true} syn = {version = "1.0", default-features = false, features = ["parsing", "printing"]} proc-macro2 = {version = "1.0", default-features = false, features = ["proc-macro"]} quote = {version = "1.0", default-features = false } -image = "*" +image = { version = "*", default-features = false } dyn-clone = "1.0" log = "0.4" diff --git a/node-graph/gstd/src/memo.rs b/node-graph/gstd/src/memo.rs index ce69c59d..1f07d488 100644 --- a/node-graph/gstd/src/memo.rs +++ b/node-graph/gstd/src/memo.rs @@ -6,14 +6,18 @@ use graphene_core::Node; /// Caches the output of a given Node and acts as a proxy #[derive(Default)] -pub struct CacheNode { +pub struct CacheNode { // We have to use an append only data structure to make sure the references // to the cache entries are always valid cache: boxcar::Vec<(u64, T)>, + node: CachedNode, } -impl<'i, T: 'i + Hash> Node<'i, T> for CacheNode { +impl<'i, T: 'i, I: 'i + Hash, CachedNode: 'i> Node<'i, I> for CacheNode +where + CachedNode: for<'any_input> Node<'any_input, I, Output = T>, +{ type Output = &'i T; - fn eval<'s: 'i>(&'s self, input: T) -> Self::Output { + fn eval<'s: 'i>(&'s self, input: I) -> Self::Output { let mut hasher = Xxh3::new(); input.hash(&mut hasher); let hash = hasher.finish(); @@ -22,15 +26,16 @@ impl<'i, T: 'i + Hash> Node<'i, T> for CacheNode { return cached_value; } else { trace!("Cache miss"); - let index = self.cache.push((hash, input)); + let output = self.node.eval(input); + let index = self.cache.push((hash, output)); return &self.cache[index].1; } } } -impl CacheNode { - pub fn new() -> CacheNode { - CacheNode { cache: boxcar::Vec::new() } +impl CacheNode { + pub fn new(node: CachedNode) -> CacheNode { + CacheNode { cache: boxcar::Vec::new(), node } } } diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 1ef2c416..d03a0a73 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -90,6 +90,33 @@ pub fn export_image_node<'i, 's: 'i>() -> impl Node<'i, 's, (Image, &'i str), Ou } */ +pub struct DownscaleNode; + +#[node_macro::node_fn(DownscaleNode)] +fn downscale(image_frame: ImageFrame) -> ImageFrame { + let target_width = image_frame.transform.transform_vector2((1., 0.).into()).length() as usize; + let target_height = image_frame.transform.transform_vector2((0., 1.).into()).length() as usize; + + let mut image = Image { + width: target_width as u32, + height: target_height as u32, + data: Vec::with_capacity(target_width * target_height), + }; + + let scale_factor = DVec2::new(image_frame.image.width as f64, image_frame.image.height as f64) / DVec2::new(target_width as f64, target_height as f64); + for y in 0..target_height { + for x in 0..target_width { + let pixel = image_frame.sample(DVec2::new(x as f64, y as f64) * scale_factor); + image.data.push(pixel); + } + } + + ImageFrame { + image, + transform: image_frame.transform, + } +} + #[derive(Debug, Clone, Copy)] pub struct MapImageNode { map_fn: MapFn, @@ -168,8 +195,8 @@ fn compute_transformed_bounding_box(transform: DAffine2) -> Bbox { } #[derive(Debug, Clone, Copy)] -pub struct BlendImageNode { - background: background, +pub struct BlendImageNode { + background: Background, map_fn: MapFn, } @@ -202,7 +229,7 @@ where } let dst_pixel = background.get_mut(x as usize, y as usize); - let src_pixel = foreground.sample(fg_point.x, fg_point.y); + let src_pixel = foreground.sample(fg_point); *dst_pixel = map_fn.eval((src_pixel, *dst_pixel)); } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index b3df0dbb..afe0da93 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -135,6 +135,7 @@ fn node_registry() -> HashMap, input: f64, params: [&f64]), register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]), register_node!(graphene_core::ops::SomeNode, input: ImageFrame, params: []), + register_node!(graphene_std::raster::DownscaleNode, input: ImageFrame, params: []), #[cfg(feature = "gpu")] register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image, params: [String]), vec![( @@ -289,21 +290,43 @@ fn node_registry() -> HashMap, input: Image, params: []), ( NodeIdentifier::new("graphene_std::memo::CacheNode"), - |_| { - let node: CacheNode = graphene_std::memo::CacheNode::new(); + |args| { + let input: DowncastBothNode<(), Image> = DowncastBothNode::new(args[0]); + let node: CacheNode = graphene_std::memo::CacheNode::new(input); let any = DynAnyRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(concrete!(Image), concrete!(&Image), vec![]), + NodeIOTypes::new(concrete!(()), concrete!(&Image), vec![(concrete!(()), concrete!(Image))]), ), ( NodeIdentifier::new("graphene_std::memo::CacheNode"), - |_| { - let node: CacheNode = graphene_std::memo::CacheNode::new(); + |args| { + let input: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); + let node: CacheNode = graphene_std::memo::CacheNode::new(input); let any = DynAnyRefNode::new(node); any.into_type_erased() }, - NodeIOTypes::new(concrete!(QuantizationChannels), concrete!(&QuantizationChannels), vec![]), + NodeIOTypes::new(concrete!(()), concrete!(&ImageFrame), vec![(concrete!(()), concrete!(ImageFrame))]), + ), + ( + NodeIdentifier::new("graphene_std::memo::CacheNode"), + |args| { + let input: DowncastBothNode = DowncastBothNode::new(args[0]); + let node: CacheNode = graphene_std::memo::CacheNode::new(input); + let any = DynAnyRefNode::new(node); + any.into_type_erased() + }, + NodeIOTypes::new(concrete!(ImageFrame), concrete!(&ImageFrame), vec![(concrete!(ImageFrame), concrete!(ImageFrame))]), + ), + ( + NodeIdentifier::new("graphene_std::memo::CacheNode"), + |args| { + let input: DowncastBothNode<(), QuantizationChannels> = DowncastBothNode::new(args[0]); + let node: CacheNode = graphene_std::memo::CacheNode::new(input); + let any = DynAnyRefNode::new(node); + any.into_type_erased() + }, + NodeIOTypes::new(concrete!(()), concrete!(&QuantizationChannels), vec![(concrete!(()), concrete!(QuantizationChannels))]), ), ], register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]),