Restructure node graph execution to be safer (#1277)

* Reorganize file structure

* Remove all unsafe code

* Add testcase for debugging ub

* Convert into proper test with fail condition

* General cleanup

* Fix tests

* Add feature guard for deallocation

* Use raw pointer for storing values to avoid violating aliasing rules

* Add comment explaining the disabling of simd128

* Fix brush node

* Fix formatting
This commit is contained in:
Dennis Kobert 2023-06-03 01:18:44 +02:00 committed by Keavon Chambers
parent 5558deba5e
commit 26473a8002
29 changed files with 363 additions and 299 deletions

8
Cargo.lock generated
View File

@ -1762,6 +1762,7 @@ dependencies = [
"derivative", "derivative",
"dyn-any", "dyn-any",
"env_logger", "env_logger",
"futures",
"glam", "glam",
"graph-craft", "graph-craft",
"graphene-core", "graphene-core",
@ -2206,6 +2207,7 @@ dependencies = [
"num-traits", "num-traits",
"once_cell", "once_cell",
"serde", "serde",
"typed-arena",
] ]
[[package]] [[package]]
@ -5010,6 +5012,12 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff"
[[package]]
name = "typed-arena"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.16.0" version = "1.16.0"

View File

@ -50,3 +50,4 @@ package = "graphite-document-legacy"
[dev-dependencies] [dev-dependencies]
env_logger = "0.8.4" env_logger = "0.8.4"
test-case = "2.1" test-case = "2.1"
futures = "0.3.28"

File diff suppressed because one or more lines are too long

View File

@ -3,8 +3,6 @@ use crate::messages::prelude::*;
use graphite_proc_macros::*; use graphite_proc_macros::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[remain::sorted] #[remain::sorted]
#[impl_message] #[impl_message]

View File

@ -1001,10 +1001,6 @@ impl DocumentMessageHandler {
// Calculate the size of the region to be exported and generate an SVG of the artwork below this layer within that region // Calculate the size of the region to be exported and generate an SVG of the artwork below this layer within that region
let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap(); let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap();
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()); let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
// TODO: Fix this hack
// This is a hack to prevent the compiler from optimizing out the size calculation which likely is due
// to undefined behavior. THIS IS NOT A FIX.
log::trace!("size: {:?}", size);
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path)); let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
self.restore_document_transform(old_transforms); self.restore_document_transform(old_transforms);

View File

@ -224,11 +224,7 @@ impl<'a> ModifyInputsContext<'a> {
fn brush_modify(&mut self, strokes: Vec<BrushStroke>) { fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
self.modify_inputs("Brush", false, |inputs| { self.modify_inputs("Brush", false, |inputs| {
if matches!(inputs[0], NodeInput::Node { .. }) { inputs[2] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false);
inputs[1] = core::mem::replace(&mut inputs[0], NodeInput::value(TaggedValue::None, false));
}
inputs[0] = NodeInput::value(TaggedValue::None, false);
inputs[3] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false);
}); });
} }
} }

View File

@ -669,9 +669,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNodeType { DocumentNodeType {
name: "Brush", name: "Brush",
category: "Brush", category: "Brush",
identifier: NodeImplementation::proto("graphene_std::brush::BrushNode"), identifier: NodeImplementation::proto("graphene_std::brush::BrushNode<_, _>"),
inputs: vec![ inputs: vec![
DocumentInputType::value("None", TaggedValue::None, false),
DocumentInputType::value("Background", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Background", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Bounds", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Bounds", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Trace", TaggedValue::BrushStrokes(Vec::new()), false), DocumentInputType::value("Trace", TaggedValue::BrushStrokes(Vec::new()), false),

View File

@ -14,7 +14,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::layer_layer::CachedOutputData; use document_legacy::layers::layer_layer::CachedOutputData;
use document_legacy::LayerId; use document_legacy::LayerId;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput, NodeNetwork}; use graph_craft::document::{NodeInput, NodeNetwork};
use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle}; use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
use graphene_core::Color; use graphene_core::Color;
@ -300,7 +300,6 @@ impl ToolTransition for BrushTool {
struct BrushToolData { struct BrushToolData {
strokes: Vec<BrushStroke>, strokes: Vec<BrushStroke>,
layer_path: Vec<LayerId>, layer_path: Vec<LayerId>,
node_path: Vec<NodeId>,
transform: DAffine2, transform: DAffine2,
} }
@ -315,7 +314,7 @@ impl BrushToolData {
let network = &layer.network; let network = &layer.network;
for (node, _node_id) in network.primary_flow() { for (node, _node_id) in network.primary_flow() {
if node.name == "Brush" { if node.name == "Brush" {
let points_input = node.inputs.get(3)?; let points_input = node.inputs.get(2)?;
let NodeInput::Value { tagged_value: TaggedValue::BrushStrokes(strokes), .. } = points_input else { let NodeInput::Value { tagged_value: TaggedValue::BrushStrokes(strokes), .. } = points_input else {
continue; continue;
}; };
@ -332,7 +331,7 @@ impl BrushToolData {
matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)).then_some(&self.layer_path) matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)).then_some(&self.layer_path)
} }
fn update_strokes(&self, brush_options: &BrushOptions, responses: &mut VecDeque<Message>) { fn update_strokes(&self, responses: &mut VecDeque<Message>) {
let layer = self.layer_path.clone(); let layer = self.layer_path.clone();
let strokes = self.strokes.clone(); let strokes = self.strokes.clone();
responses.add(GraphOperationMessage::Brush { layer, strokes }); responses.add(GraphOperationMessage::Brush { layer, strokes });
@ -394,7 +393,7 @@ impl Fsm for BrushToolFsmState {
if new_layer { if new_layer {
add_brush_render(tool_options, tool_data, responses); add_brush_render(tool_options, tool_data, responses);
} }
tool_data.update_strokes(tool_options, responses); tool_data.update_strokes(responses);
BrushToolFsmState::Drawing BrushToolFsmState::Drawing
} }
@ -403,7 +402,7 @@ impl Fsm for BrushToolFsmState {
if let Some(stroke) = tool_data.strokes.last_mut() { if let Some(stroke) = tool_data.strokes.last_mut() {
stroke.trace.push(BrushInputSample { position: layer_position }) stroke.trace.push(BrushInputSample { position: layer_position })
} }
tool_data.update_strokes(tool_options, responses); tool_data.update_strokes(responses);
BrushToolFsmState::Drawing BrushToolFsmState::Drawing
} }
@ -448,7 +447,7 @@ impl Fsm for BrushToolFsmState {
} }
} }
fn add_brush_render(tool_options: &BrushOptions, data: &BrushToolData, responses: &mut VecDeque<Message>) { fn add_brush_render(_tool_options: &BrushOptions, data: &BrushToolData, responses: &mut VecDeque<Message>) {
let mut network = NodeNetwork::default(); let mut network = NodeNetwork::default();
let output_node = network.push_output_node(); let output_node = network.push_output_node();
if let Some(node) = network.nodes.get_mut(&output_node) { if let Some(node) = network.nodes.get_mut(&output_node) {

View File

@ -9,7 +9,7 @@ use document_legacy::{LayerId, Operation};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork}; use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
use graph_craft::executor::Compiler; use graph_craft::graphene_compiler::Compiler;
use graph_craft::{concrete, Type, TypeDescriptor}; use graph_craft::{concrete, Type, TypeDescriptor};
use graphene_core::application_io::ApplicationIo; use graphene_core::application_io::ApplicationIo;
use graphene_core::raster::{Image, ImageFrame}; use graphene_core::raster::{Image, ImageFrame};
@ -19,7 +19,7 @@ use graphene_core::vector::style::ViewMode;
use graphene_core::wasm_application_io::WasmApplicationIo; use graphene_core::wasm_application_io::WasmApplicationIo;
use graphene_core::{Color, EditorApi, SurfaceFrame, SurfaceId}; use graphene_core::{Color, EditorApi, SurfaceFrame, SurfaceId};
use interpreted_executor::executor::DynamicExecutor; use interpreted_executor::dynamic_executor::DynamicExecutor;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use std::borrow::Cow; use std::borrow::Cow;
@ -147,7 +147,7 @@ impl NodeRuntime {
return Err(e); return Err(e);
} }
use graph_craft::executor::Executor; use graph_craft::graphene_compiler::Executor;
let result = match self.executor.input_type() { let result = match self.executor.input_type() {
Some(t) if t == concrete!(EditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()), Some(t) if t == concrete!(EditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()),
@ -155,7 +155,7 @@ impl NodeRuntime {
_ => Err("Invalid input type".to_string()), _ => Err("Invalid input type".to_string()),
}?; }?;
if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) = result { if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result {
let old_id = self.canvas_cache.insert(path.to_vec(), surface_id); let old_id = self.canvas_cache.insert(path.to_vec(), surface_id);
if let Some(old_id) = old_id { if let Some(old_id) = old_id {
if old_id != surface_id { if old_id != surface_id {
@ -206,6 +206,19 @@ impl NodeRuntime {
} }
} }
} }
pub fn introspect_node(path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
NODE_RUNTIME
.try_with(|runtime| {
let runtime = runtime.try_borrow();
if let Ok(ref runtime) = runtime {
if let Some(ref mut runtime) = runtime.as_ref() {
return runtime.executor.introspect(path).flatten();
}
}
None
})
.unwrap_or(None)
}
pub async fn run_node_graph() { pub async fn run_node_graph() {
let result = NODE_RUNTIME.try_with(|runtime| { let result = NODE_RUNTIME.try_with(|runtime| {
@ -226,7 +239,6 @@ pub async fn run_node_graph() {
#[derive(Debug)] #[derive(Debug)]
pub struct NodeGraphExecutor { pub struct NodeGraphExecutor {
pub(crate) executor: DynamicExecutor,
sender: Sender<NodeRuntimeMessage>, sender: Sender<NodeRuntimeMessage>,
receiver: Receiver<GenerationResponse>, receiver: Receiver<GenerationResponse>,
// TODO: This is a memory leak since layers are never removed // TODO: This is a memory leak since layers are never removed
@ -250,7 +262,6 @@ impl Default for NodeGraphExecutor {
}); });
Self { Self {
executor: Default::default(),
futures: Default::default(), futures: Default::default(),
sender: request_sender, sender: request_sender,
receiver: response_reciever, receiver: response_reciever,
@ -275,12 +286,12 @@ impl NodeGraphExecutor {
generation_id generation_id
} }
pub fn update_font_cache(&self, font_cache: FontCache) { pub fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache)).expect("Failed to send font cache update"); introspect_node(path)
} }
pub fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> { pub fn update_font_cache(&self, font_cache: FontCache) {
self.executor.introspect(path).flatten() self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache)).expect("Failed to send font cache update");
} }
pub fn previous_output_type(&self, path: &[LayerId]) -> Option<Type> { pub fn previous_output_type(&self, path: &[LayerId]) -> Option<Type> {

View File

@ -1,6 +1,14 @@
[target.wasm32-unknown-unknown] [target.wasm32-unknown-unknown]
#rustflags = ["-C", "target-feature=+simd128,+atomics,+bulk-memory,+mutable-globals","--cfg=web_sys_unstable_apis"] #rustflags = ["-C", "target-feature=+simd128,+atomics,+bulk-memory,+mutable-globals","--cfg=web_sys_unstable_apis"]
rustflags = ["-C", "target-feature=+simd128","--cfg=web_sys_unstable_apis"] rustflags = [
# Currently disabled because of https://github.com/GraphiteEditor/Graphite/issues/1262
# The current simd implementation leads to undefined behavior
#"-C",
#"target-feature=+simd128",
"-C",
"link-arg=--max-memory=4294967296",
"--cfg=web_sys_unstable_apis",
]
[unstable] [unstable]
build-std = ["panic_abort", "std"] build-std = ["panic_abort", "std"]

View File

@ -56,13 +56,13 @@ impl<'a, T: DynAny<'a> + 'a> UpcastFrom<T> for dyn DynAny<'a> + 'a {
} }
} }
pub trait DynAny<'a> { pub trait DynAny<'a>: 'a {
fn type_id(&self) -> TypeId; fn type_id(&self) -> TypeId;
#[cfg(feature = "log-bad-types")] #[cfg(feature = "log-bad-types")]
fn type_name(&self) -> &'static str; fn type_name(&self) -> &'static str;
} }
impl<'a, T: StaticType> DynAny<'a> for T { impl<'a, T: StaticType + 'a> DynAny<'a> for T {
fn type_id(&self) -> core::any::TypeId { fn type_id(&self) -> core::any::TypeId {
core::any::TypeId::of::<T::Static>() core::any::TypeId::of::<T::Static>()
} }
@ -71,7 +71,7 @@ impl<'a, T: StaticType> DynAny<'a> for T {
core::any::type_name::<T>() core::any::type_name::<T>()
} }
} }
pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> { pub fn downcast_ref<'a, V: StaticType + 'a>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
if i.type_id() == core::any::TypeId::of::<<V as StaticType>::Static>() { if i.type_id() == core::any::TypeId::of::<<V as StaticType>::Static>() {
// SAFETY: caller guarantees that T is the correct type // SAFETY: caller guarantees that T is the correct type
let ptr = i as *const dyn DynAny<'a> as *const V; let ptr = i as *const dyn DynAny<'a> as *const V;
@ -82,7 +82,7 @@ pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn downcast<'a, V: StaticType>(i: Box<dyn DynAny<'a> + 'a>) -> Result<Box<V>, String> { pub fn downcast<'a, V: StaticType + 'a>(i: Box<dyn DynAny<'a> + 'a>) -> Result<Box<V>, String> {
let type_id = DynAny::type_id(i.as_ref()); let type_id = DynAny::type_id(i.as_ref());
if type_id == core::any::TypeId::of::<<V as StaticType>::Static>() { if type_id == core::any::TypeId::of::<<V as StaticType>::Static>() {
// SAFETY: caller guarantees that T is the correct type // SAFETY: caller guarantees that T is the correct type

View File

@ -14,7 +14,7 @@ fn main() {
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
let network = add_network(); let network = add_network();
let compiler = graph_craft::executor::Compiler {}; let compiler = graph_craft::graphene_compiler::Compiler {};
let proto_network = compiler.compile_single(network, true).unwrap(); let proto_network = compiler.compile_single(network, true).unwrap();
let io = ShaderIO { let io = ShaderIO {

View File

@ -96,13 +96,6 @@ where
{ {
} }
/*impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn for<'n> Node<'n, I, Output = O> {
type Output = O;
fn eval(&'i self, input: I) -> Self::Output {
(**self).eval(input)
}
}*/
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for &'s N { impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for &'s N {
type Output = O; type Output = O;
@ -118,8 +111,16 @@ impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for Box<N
(**self).eval(input) (**self).eval(input)
} }
} }
#[cfg(feature = "alloc")]
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for alloc::sync::Arc<N> {
type Output = O;
impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn for<'a> Node<'a, I, Output = O> { fn eval(&'i self, input: I) -> Self::Output {
(**self).eval(input)
}
}
impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn Node<'i, I, Output = O> {
type Output = O; type Output = O;
fn eval(&'i self, input: I) -> Self::Output { fn eval(&'i self, input: I) -> Self::Output {
@ -130,7 +131,7 @@ use core::pin::Pin;
use dyn_any::StaticTypeSized; use dyn_any::StaticTypeSized;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<Box<dyn for<'a> Node<'a, I, Output = O> + 'i>> { impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {
type Output = O; type Output = O;
fn eval(&'i self, input: I) -> Self::Output { fn eval(&'i self, input: I) -> Self::Output {

View File

@ -5,8 +5,9 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[features] [features]
default = [] default = ["dealloc_nodes"]
serde = ["dep:serde", "graphene-core/serde", "glam/serde", "bezier-rs/serde"] serde = ["dep:serde", "graphene-core/serde", "glam/serde", "bezier-rs/serde"]
dealloc_nodes = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -892,7 +892,7 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::{cell::Cell, sync::atomic::AtomicU64}; use std::sync::atomic::AtomicU64;
use super::*; use super::*;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
@ -1193,7 +1193,7 @@ mod test {
.collect(), .collect(),
..Default::default() ..Default::default()
}; };
let mut new_ids = 101..; let _new_ids = 101..;
network.flatten_with_fns(1, |self_id, inner_id| self_id * 10 + inner_id, || 10000); network.flatten_with_fns(1, |self_id, inner_id| self_id * 10 + inner_id, || 10000);
network.flatten_with_fns(2, |self_id, inner_id| self_id * 10 + inner_id, || 10001); network.flatten_with_fns(2, |self_id, inner_id| self_id * 10 + inner_id, || 10001);
network.remove_dead_nodes(); network.remove_dead_nodes();

View File

@ -1,5 +1,5 @@
use super::DocumentNode; use super::DocumentNode;
use crate::executor::Any; use crate::graphene_compiler::Any;
pub use crate::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus}; pub use crate::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus};
use crate::proto::{Any as DAny, FutureAny}; use crate::proto::{Any as DAny, FutureAny};

View File

@ -8,5 +8,5 @@ pub use graphene_core::{concrete, generic, NodeIdentifier, Type, TypeDescriptor}
pub mod document; pub mod document;
pub mod proto; pub mod proto;
pub mod executor; pub mod graphene_compiler;
pub mod imaginate_input; pub mod imaginate_input;

View File

@ -1,5 +1,8 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use std::sync::Arc;
use std::hash::Hash; use std::hash::Hash;
use xxhash_rust::xxh3::Xxh3; use xxhash_rust::xxh3::Xxh3;
@ -18,9 +21,59 @@ pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
pub type FutureAny<'n> = DynFuture<'n, Any<'n>>; pub type FutureAny<'n> = DynFuture<'n, Any<'n>>;
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n; pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n;
pub type TypeErasedPinnedRef<'n> = Pin<&'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n)>; pub type TypeErasedPinnedRef<'n> = Pin<&'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n)>;
pub type TypeErasedRef<'n> = &'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n);
pub type TypeErasedBox<'n> = Box<dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n>;
pub type TypeErasedPinned<'n> = Pin<Box<dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n>>; pub type TypeErasedPinned<'n> = Pin<Box<dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n>>;
pub type NodeConstructor = for<'a> fn(Vec<TypeErasedPinnedRef<'static>>) -> DynFuture<'static, TypeErasedPinned<'static>>; pub type NodeConstructor = for<'a> fn(Vec<Arc<NodeContainer>>) -> DynFuture<'static, TypeErasedBox<'static>>;
#[derive(Clone)]
pub struct NodeContainer {
#[cfg(feature = "dealloc_nodes")]
pub node: *mut TypeErasedNode<'static>,
#[cfg(not(feature = "dealloc_nodes"))]
pub node: TypeErasedRef<'static>,
}
impl Deref for NodeContainer {
type Target = TypeErasedNode<'static>;
#[cfg(feature = "dealloc_nodes")]
fn deref(&self) -> &Self::Target {
unsafe { &*(self.node as *const TypeErasedNode) }
#[cfg(not(feature = "dealloc_nodes"))]
self.node
}
#[cfg(not(feature = "dealloc_nodes"))]
fn deref(&self) -> &Self::Target {
self.node
}
}
#[cfg(feature = "dealloc_nodes")]
impl Drop for NodeContainer {
fn drop(&mut self) {
unsafe { self.dealloc_unchecked() }
}
}
impl core::fmt::Debug for NodeContainer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeContainer").finish()
}
}
impl NodeContainer {
pub fn new(node: TypeErasedBox<'static>) -> Arc<Self> {
let node = Box::leak(node);
Arc::new(Self { node })
}
#[cfg(feature = "dealloc_nodes")]
unsafe fn dealloc_unchecked(&mut self) {
std::mem::drop(Box::from_raw(self.node));
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Default, PartialEq, Clone, Hash, Eq)] #[derive(Debug, Default, PartialEq, Clone, Hash, Eq)]
@ -406,7 +459,7 @@ impl ProtoNetwork {
} }
/// The `TypingContext` is used to store the types of the nodes indexed by their stable node id. /// The `TypingContext` is used to store the types of the nodes indexed by their stable node id.
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Default, Clone)]
pub struct TypingContext { pub struct TypingContext {
lookup: Cow<'static, HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>, lookup: Cow<'static, HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>,
inferred: HashMap<NodeId, NodeIOTypes>, inferred: HashMap<NodeId, NodeIOTypes>,
@ -539,7 +592,7 @@ impl TypingContext {
dbg!(&self.inferred); dbg!(&self.inferred);
Err(format!( Err(format!(
"No implementations found for {identifier} with \ninput: {input:?} and \nparameters: {parameters:?}.\nOther Implementations found: {:?}", "No implementations found for {identifier} with \ninput: {input:?} and \nparameters: {parameters:?}.\nOther Implementations found: {:?}",
impls, impls.keys().collect::<Vec<_>>(),
)) ))
} }
[(org_nio, output)] => { [(org_nio, output)] => {

View File

@ -1,9 +1,9 @@
use dyn_any::StaticType; use dyn_any::StaticType;
pub use graph_craft::proto::{Any, TypeErasedNode, TypeErasedPinned, TypeErasedPinnedRef}; pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny}; use graph_craft::proto::{DynFuture, FutureAny};
use graphene_core::NodeIO; use graphene_core::NodeIO;
pub use graphene_core::{generic, ops, Node}; pub use graphene_core::{generic, ops, Node};
use std::marker::PhantomData; use std::{marker::PhantomData, sync::Arc};
pub struct DynAnyNode<I, O, Node> { pub struct DynAnyNode<I, O, Node> {
node: Node, node: Node,
@ -124,15 +124,15 @@ impl<'i, N> FutureWrapperNode<N> {
} }
pub trait IntoTypeErasedNode<'n> { pub trait IntoTypeErasedNode<'n> {
fn into_type_erased(self) -> TypeErasedPinned<'n>; fn into_type_erased(self) -> TypeErasedBox<'n>;
} }
impl<'n, N: 'n> IntoTypeErasedNode<'n> for N impl<'n, N: 'n> IntoTypeErasedNode<'n> for N
where where
N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n, N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n,
{ {
fn into_type_erased(self) -> TypeErasedPinned<'n> { fn into_type_erased(self) -> TypeErasedBox<'n> {
Box::pin(self) Box::new(self)
} }
} }
@ -159,13 +159,13 @@ where
/// Boxes the input and downcasts the output. /// Boxes the input and downcasts the output.
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny> /// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
#[derive(Clone, Copy)] #[derive(Clone)]
pub struct DowncastBothNode<'a, I, O> { pub struct DowncastBothNode<I, O> {
node: TypeErasedPinnedRef<'a>, node: Arc<NodeContainer>,
_i: PhantomData<I>, _i: PhantomData<I>,
_o: PhantomData<O>, _o: PhantomData<O>,
} }
impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothNode<'n, I, O> { impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothNode<I, O> {
type Output = DynFuture<'input, O>; type Output = DynFuture<'input, O>;
#[inline] #[inline]
fn eval(&'input self, input: I) -> Self::Output { fn eval(&'input self, input: I) -> Self::Output {
@ -180,8 +180,8 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i
} }
} }
} }
impl<'n, I, O> DowncastBothNode<'n, I, O> { impl<I, O> DowncastBothNode<I, O> {
pub const fn new(node: TypeErasedPinnedRef<'n>) -> Self { pub const fn new(node: Arc<NodeContainer>) -> Self {
Self { Self {
node, node,
_i: core::marker::PhantomData, _i: core::marker::PhantomData,
@ -191,12 +191,12 @@ impl<'n, I, O> DowncastBothNode<'n, I, O> {
} }
/// Boxes the input and downcasts the output. /// Boxes the input and downcasts the output.
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny> /// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
#[derive(Clone, Copy)] #[derive(Clone)]
pub struct DowncastBothRefNode<'a, I, O> { pub struct DowncastBothRefNode<I, O> {
node: TypeErasedPinnedRef<'a>, node: Arc<NodeContainer>,
_i: PhantomData<(I, O)>, _i: PhantomData<(I, O)>,
} }
impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothRefNode<'n, I, O> { impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothRefNode<I, O> {
type Output = DynFuture<'input, &'input O>; type Output = DynFuture<'input, &'input O>;
#[inline] #[inline]
fn eval(&'input self, input: I) -> Self::Output { fn eval(&'input self, input: I) -> Self::Output {
@ -210,18 +210,18 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i
} }
} }
} }
impl<'n, I, O> DowncastBothRefNode<'n, I, O> { impl<I, O> DowncastBothRefNode<I, O> {
pub const fn new(node: TypeErasedPinnedRef<'n>) -> Self { pub const fn new(node: Arc<NodeContainer>) -> Self {
Self { node, _i: core::marker::PhantomData } Self { node, _i: core::marker::PhantomData }
} }
} }
pub struct ComposeTypeErased<'a> { pub struct ComposeTypeErased {
first: TypeErasedPinnedRef<'a>, first: Arc<NodeContainer>,
second: TypeErasedPinnedRef<'a>, second: Arc<NodeContainer>,
} }
impl<'i, 'a: 'i> Node<'i, Any<'i>> for ComposeTypeErased<'a> { impl<'i, 'a: 'i> Node<'i, Any<'i>> for ComposeTypeErased {
type Output = DynFuture<'i, Any<'i>>; type Output = DynFuture<'i, Any<'i>>;
fn eval(&'i self, input: Any<'i>) -> Self::Output { fn eval(&'i self, input: Any<'i>) -> Self::Output {
Box::pin(async move { Box::pin(async move {
@ -231,13 +231,13 @@ impl<'i, 'a: 'i> Node<'i, Any<'i>> for ComposeTypeErased<'a> {
} }
} }
impl<'a> ComposeTypeErased<'a> { impl ComposeTypeErased {
pub const fn new(first: TypeErasedPinnedRef<'a>, second: TypeErasedPinnedRef<'a>) -> Self { pub const fn new(first: Arc<NodeContainer>, second: Arc<NodeContainer>) -> Self {
ComposeTypeErased { first, second } ComposeTypeErased { first, second }
} }
} }
pub fn input_node<O: StaticType>(n: TypeErasedPinnedRef) -> DowncastBothNode<(), O> { pub fn input_node<O: StaticType>(n: Arc<NodeContainer>) -> DowncastBothNode<(), O> {
DowncastBothNode::new(n) DowncastBothNode::new(n)
} }
@ -267,22 +267,23 @@ mod test {
//let add = DynAnyNode::new(AddNode::new()).into_type_erased(); //let add = DynAnyNode::new(AddNode::new()).into_type_erased();
//add.eval(Box::new(&("32", 32u32))); //add.eval(Box::new(&("32", 32u32)));
let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(ValueNode::new(FutureWrapperNode { node: AddNode::new() })); let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(ValueNode::new(FutureWrapperNode { node: AddNode::new() }));
let type_erased = Box::pin(dyn_any) as TypeErasedPinned; let type_erased = Box::new(dyn_any) as TypeErasedBox;
let _ref_type_erased = type_erased.as_ref(); let _ref_type_erased = type_erased.as_ref();
//let type_erased = Box::pin(dyn_any) as TypeErasedPinned<'_>; //let type_erased = Box::pin(dyn_any) as TypeErasedBox<'_>;
type_erased.eval(Box::new(&("32", 32u32))); type_erased.eval(Box::new(&("32", 32u32)));
} }
#[test] #[test]
pub fn dyn_input_invalid_eval_panic_() { pub fn dyn_input_compose() {
//let add = DynAnyNode::new(AddNode::new()).into_type_erased(); //let add = DynAnyNode::new(AddNode::new()).into_type_erased();
//add.eval(Box::new(&("32", 32u32))); //add.eval(Box::new(&("32", 32u32)));
let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(ValueNode::new(FutureWrapperNode { node: AddNode::new() })); let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(ValueNode::new(FutureWrapperNode { node: AddNode::new() }));
let type_erased = Box::pin(dyn_any) as TypeErasedPinned<'_>; let type_erased = Box::new(dyn_any) as TypeErasedBox<'_>;
type_erased.eval(Box::new((4u32, 2u32))); type_erased.eval(Box::new((4u32, 2u32)));
let id_node = FutureWrapperNode::new(IdNode::new()); let id_node = FutureWrapperNode::new(IdNode::new());
let type_erased_id = Box::pin(id_node) as TypeErasedPinned; let any_id = DynAnyNode::<u32, u32, _>::new(ValueNode::new(id_node));
let type_erased = ComposeTypeErased::new(type_erased.as_ref(), type_erased_id.as_ref()); let type_erased_id = Box::new(any_id) as TypeErasedBox;
let type_erased = ComposeTypeErased::new(NodeContainer::new(type_erased), NodeContainer::new(type_erased_id));
type_erased.eval(Box::new((4u32, 2u32))); type_erased.eval(Box::new((4u32, 2u32)));
//let downcast: DowncastBothNode<(u32, u32), u32> = DowncastBothNode::new(type_erased.as_ref()); //let downcast: DowncastBothNode<(u32, u32), u32> = DowncastBothNode::new(type_erased.as_ref());
//downcast.eval((4u32, 2u32)); //downcast.eval((4u32, 2u32));

View File

@ -1,11 +1,12 @@
use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode}; use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, ExtendImageNode};
use graphene_core::raster::adjustments::blend_colors; use graphene_core::raster::adjustments::blend_colors;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::{Alpha, Color, Image, ImageFrame, Pixel, Sample}; use graphene_core::raster::{Alpha, Color, Image, ImageFrame, Pixel, Sample};
use graphene_core::raster::{BlendMode, BlendNode}; use graphene_core::raster::{BlendMode, BlendNode};
use graphene_core::transform::{Transform, TransformMut}; use graphene_core::transform::{Transform, TransformMut};
use graphene_core::value::{CopiedNode, ValueNode}; use graphene_core::value::{ClonedNode, CopiedNode, OnceCellNode, ValueNode};
use graphene_core::vector::brush_stroke::BrushStyle; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
use graphene_core::vector::VectorData; use graphene_core::vector::VectorData;
use graphene_core::Node; use graphene_core::Node;
use node_macro::node_fn; use node_macro::node_fn;
@ -279,6 +280,89 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
) )
} }
pub struct BrushNode<Bounds, Strokes> {
bounds: Bounds,
strokes: Strokes,
}
#[node_macro::node_fn(BrushNode)]
async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec<BrushStroke>) -> ImageFrame<Color> {
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let image_bbox = Bbox::from_transform(image.transform).to_axis_aligned_bbox();
let bbox = stroke_bbox.union(&image_bbox);
let mut background_bounds = bbox.to_transform();
if bounds.transform != DAffine2::ZERO {
background_bounds = bounds.transform;
}
let has_erase_strokes = strokes.iter().any(|s| s.style.blend_mode == BlendMode::Erase);
let blank_image = ImageFrame {
image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::TRANSPARENT),
transform: background_bounds,
};
let opaque_image = ImageFrame {
image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE),
transform: background_bounds,
};
let mut erase_restore_mask = has_erase_strokes.then_some(opaque_image);
let mut actual_image = ExtendImageNode::new(OnceCellNode::new(blank_image)).eval(image);
for stroke in strokes {
let normal_blend = BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
// Create brush texture.
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
let brush_texture = create_brush_texture(stroke.style.clone());
// Compute transformation from stroke texture space into layer space, and create the stroke texture.
let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();
let mut bbox = stroke.bounding_box();
bbox.start = bbox.start.floor();
bbox.end = bbox.end.floor();
let stroke_size = bbox.size() + DVec2::splat(stroke.style.diameter);
// For numerical stability we want to place the first blit point at a stable, integer offset
// in layer space.
let snap_offset = positions[0].floor() - positions[0];
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
match stroke.style.blend_mode {
BlendMode::Erase => {
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = Some(blit_node.eval(mask));
}
}
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => {
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = Some(blit_node.eval(mask));
}
}
blend_mode => {
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend));
let empty_stroke_texture = EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)).eval(stroke_to_layer);
let stroke_texture = blit_node.eval(empty_stroke_texture);
// TODO: Is this the correct way to do opacity in blending?
actual_image = blend_with_mode(actual_image, stroke_texture, blend_mode, stroke.style.color.a() * 100.);
}
}
}
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.));
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
actual_image = blend_executor.eval((actual_image, mask));
}
actual_image
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@ -18,7 +18,7 @@ pub struct GpuCompiler<TypingContext, ShaderIO> {
// TODO: Move to graph-craft // TODO: Move to graph-craft
#[node_macro::node_fn(GpuCompiler)] #[node_macro::node_fn(GpuCompiler)]
async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingContext, io: ShaderIO) -> compilation_client::Shader { async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingContext, io: ShaderIO) -> compilation_client::Shader {
let compiler = graph_craft::executor::Compiler {}; let compiler = graph_craft::graphene_compiler::Compiler {};
let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() }; let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() };
let proto_networks: Vec<_> = compiler.compile(network.clone(), true).collect(); let proto_networks: Vec<_> = compiler.compile(network.clone(), true).collect();
@ -44,7 +44,7 @@ pub struct MapGpuNode<Node> {
#[node_macro::node_fn(MapGpuNode)] #[node_macro::node_fn(MapGpuNode)]
async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Color> { async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Color> {
log::debug!("Executing gpu node"); log::debug!("Executing gpu node");
let compiler = graph_craft::executor::Compiler {}; let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node); let inner_network = NodeNetwork::value_network(node);
log::debug!("inner_network: {:?}", inner_network); log::debug!("inner_network: {:?}", inner_network);
@ -262,7 +262,7 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
let translation: Vec2 = bg_to_fg.translation.as_vec2(); let translation: Vec2 = bg_to_fg.translation.as_vec2();
log::debug!("Executing gpu blend node!"); log::debug!("Executing gpu blend node!");
let compiler = graph_craft::executor::Compiler {}; let compiler = graph_craft::graphene_compiler::Compiler {};
let network = NodeNetwork { let network = NodeNetwork {
inputs: vec![], inputs: vec![],

View File

@ -274,7 +274,7 @@ where
fn blend_image<_P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: RasterMut<Pixel = _P> + Transform + Sample<Pixel = _P>>( fn blend_image<_P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: RasterMut<Pixel = _P> + Transform + Sample<Pixel = _P>>(
foreground: Frame, foreground: Frame,
mut background: Background, background: Background,
map_fn: &MapFn, map_fn: &MapFn,
) -> Background ) -> Background
where where

View File

@ -27,3 +27,4 @@ serde = { version = "1", features = ["derive"], optional = true }
glam = { version = "0.22" } glam = { version = "0.22" }
once_cell = "1.17.0" once_cell = "1.17.0"
futures = "0.3.28" futures = "0.3.28"
typed-arena = "2.0.2"

View File

@ -1,18 +1,17 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::error::Error; use std::error::Error;
use std::sync::{Arc, RwLock};
use std::sync::Arc;
use dyn_any::StaticType; use dyn_any::StaticType;
use graph_craft::document::value::{TaggedValue, UpcastNode}; use graph_craft::document::value::{TaggedValue, UpcastNode};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graph_craft::executor::Executor; use graph_craft::graphene_compiler::Executor;
use graph_craft::proto::{ConstructionArgs, LocalFuture, ProtoNetwork, ProtoNode, TypingContext}; use graph_craft::proto::{ConstructionArgs, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, TypeErasedBox, TypingContext};
use graph_craft::Type; use graph_craft::Type;
use graphene_std::any::{TypeErasedPinned, TypeErasedPinnedRef};
use crate::node_registry; use crate::node_registry;
#[derive(Debug, Clone)]
pub struct DynamicExecutor { pub struct DynamicExecutor {
output: NodeId, output: NodeId,
tree: BorrowTree, tree: BorrowTree,
@ -79,45 +78,14 @@ impl<'a, I: StaticType + 'a> Executor<I, TaggedValue> for &'a DynamicExecutor {
} }
} }
pub struct NodeContainer<'n> { #[derive(Default)]
pub node: TypeErasedPinned<'n>,
// the dependencies are only kept to ensure that the nodes are not dropped while still in use
_dependencies: Vec<Arc<RwLock<NodeContainer<'static>>>>,
}
impl<'a> core::fmt::Debug for NodeContainer<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeContainer").finish()
}
}
impl<'a> NodeContainer<'a> {
pub fn new(node: TypeErasedPinned<'a>, _dependencies: Vec<Arc<RwLock<NodeContainer<'static>>>>) -> Self {
Self { node, _dependencies }
}
/// Return a static reference to the TypeErasedNode
/// # Safety
/// This is unsafe because the returned reference is only valid as long as the NodeContainer is alive
pub unsafe fn erase_lifetime(self) -> NodeContainer<'static> {
std::mem::transmute(self)
}
}
impl NodeContainer<'static> {
pub unsafe fn static_ref(&self) -> TypeErasedPinnedRef<'static> {
let s = &*(self as *const Self);
*(&s.node.as_ref() as *const TypeErasedPinnedRef<'static>)
}
}
#[derive(Default, Debug, Clone)]
pub struct BorrowTree { pub struct BorrowTree {
nodes: HashMap<NodeId, Arc<RwLock<NodeContainer<'static>>>>, nodes: HashMap<NodeId, Arc<NodeContainer>>,
source_map: HashMap<Vec<NodeId>, NodeId>, source_map: HashMap<Vec<NodeId>, NodeId>,
} }
impl BorrowTree { impl BorrowTree {
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<Self, String> { pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, String> {
let mut nodes = BorrowTree::default(); let mut nodes = BorrowTree::default();
for (id, node) in proto_network.nodes { for (id, node) in proto_network.nodes {
nodes.push_node(id, node, typing_context).await? nodes.push_node(id, node, typing_context).await?
@ -133,9 +101,7 @@ impl BorrowTree {
self.push_node(id, node, typing_context).await?; self.push_node(id, node, typing_context).await?;
} else { } else {
let Some(node_container) = self.nodes.get_mut(&id) else { continue }; let Some(node_container) = self.nodes.get_mut(&id) else { continue };
let mut node_container_writer = node_container.write().unwrap(); node_container.reset();
let node = node_container_writer.node.as_mut();
node.reset();
} }
old_nodes.remove(&id); old_nodes.remove(&id);
} }
@ -143,40 +109,32 @@ impl BorrowTree {
Ok(old_nodes.into_iter().collect()) Ok(old_nodes.into_iter().collect())
} }
fn node_refs(&self, nodes: &[NodeId]) -> Vec<TypeErasedPinnedRef<'static>> { fn node_deps(&self, nodes: &[NodeId]) -> Vec<Arc<NodeContainer>> {
self.node_deps(nodes).into_iter().map(|node| unsafe { node.read().unwrap().static_ref() }).collect()
}
fn node_deps(&self, nodes: &[NodeId]) -> Vec<Arc<RwLock<NodeContainer<'static>>>> {
nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect() nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect()
} }
fn store_node(&mut self, node: Arc<RwLock<NodeContainer<'static>>>, id: NodeId) -> Arc<RwLock<NodeContainer<'static>>> { fn store_node(&mut self, node: Arc<NodeContainer>, id: NodeId) {
self.nodes.insert(id, node.clone()); self.nodes.insert(id, node);
node
} }
pub fn introspect(&self, node_path: &[NodeId]) -> Option<Option<Arc<dyn std::any::Any>>> { pub fn introspect(&self, node_path: &[NodeId]) -> Option<Option<Arc<dyn std::any::Any>>> {
let id = self.source_map.get(node_path)?; let id = self.source_map.get(node_path)?;
let node = self.nodes.get(id)?; let node = self.nodes.get(id)?;
let reader = node.read().unwrap();
let node = reader.node.as_ref();
Some(node.serialize()) Some(node.serialize())
} }
pub fn get(&self, id: NodeId) -> Option<Arc<RwLock<NodeContainer<'static>>>> { pub fn get(&self, id: NodeId) -> Option<Arc<NodeContainer>> {
self.nodes.get(&id).cloned() self.nodes.get(&id).cloned()
} }
pub async fn eval<'i, I: StaticType + 'i, O: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Option<O> { pub async fn eval<'i, I: StaticType + 'i, O: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Option<O> {
let node = self.nodes.get(&id).cloned()?; let node = self.nodes.get(&id).cloned()?;
let reader = node.read().unwrap(); let output = node.eval(Box::new(input));
let output = reader.node.eval(Box::new(input));
dyn_any::downcast::<O>(output.await).ok().map(|o| *o) dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
} }
pub async fn eval_tagged_value<'i, I: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Result<TaggedValue, String> { pub async fn eval_tagged_value<'i, I: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Result<TaggedValue, String> {
let node = self.nodes.get(&id).cloned().ok_or_else(|| "Output node not found in executor")?; let node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
let reader = node.read().unwrap(); let output = node.eval(Box::new(input));
let output = reader.node.eval(Box::new(input));
TaggedValue::try_from_any(output.await) TaggedValue::try_from_any(output.await)
} }
@ -196,23 +154,18 @@ impl BorrowTree {
match construction_args { match construction_args {
ConstructionArgs::Value(value) => { ConstructionArgs::Value(value) => {
let upcasted = UpcastNode::new(value); let upcasted = UpcastNode::new(value);
let node = Box::pin(upcasted) as TypeErasedPinned<'_>; let node = Box::new(upcasted) as TypeErasedBox<'_>;
let node = NodeContainer { node, _dependencies: vec![] }; let node = NodeContainer::new(node);
let node = unsafe { node.erase_lifetime() }; self.store_node(node.into(), id);
self.store_node(Arc::new(node.into()), id);
} }
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(ids) => { ConstructionArgs::Nodes(ids) => {
let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect(); let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect();
let construction_nodes = self.node_refs(&ids); let construction_nodes = self.node_deps(&ids);
let constructor = typing_context.constructor(id).ok_or(format!("No constructor found for node {:?}", identifier))?; let constructor = typing_context.constructor(id).ok_or(format!("No constructor found for node {:?}", identifier))?;
let node = constructor(construction_nodes).await; let node = constructor(construction_nodes).await;
let node = NodeContainer { let node = NodeContainer::new(node);
node, self.store_node(node.into(), id);
_dependencies: self.node_deps(&ids),
};
let node = unsafe { node.erase_lifetime() };
self.store_node(Arc::new(node.into()), id);
} }
}; };
Ok(()) Ok(())

View File

@ -1,7 +1,4 @@
#[macro_use] pub mod dynamic_executor;
extern crate log;
pub mod executor;
pub mod node_registry; pub mod node_registry;
#[cfg(test)] #[cfg(test)]
@ -72,8 +69,8 @@ mod tests {
..Default::default() ..Default::default()
}; };
use crate::executor::DynamicExecutor; use crate::dynamic_executor::DynamicExecutor;
use graph_craft::executor::{Compiler, Executor}; use graph_craft::graphene_compiler::{Compiler, Executor};
let compiler = Compiler {}; let compiler = Compiler {};
let protograph = compiler.compile_single(network, true).expect("Graph should be generated"); let protograph = compiler.compile_single(network, true).expect("Graph should be generated");
@ -120,12 +117,12 @@ mod tests {
..Default::default() ..Default::default()
}; };
use crate::executor::DynamicExecutor; use crate::dynamic_executor::DynamicExecutor;
use graph_craft::executor::Compiler; use graph_craft::graphene_compiler::Compiler;
let compiler = Compiler {}; let compiler = Compiler {};
let protograph = compiler.compile_single(network, true).expect("Graph should be generated"); let protograph = compiler.compile_single(network, true).expect("Graph should be generated");
let _exec = block_on(DynamicExecutor::new(protograph)).map(|e| panic!("The network should not type check: {:#?}", e)).unwrap_err(); let _exec = block_on(DynamicExecutor::new(protograph)).map(|e| panic!("The network should not type check ")).unwrap_err();
} }
} }

View File

@ -1,7 +1,7 @@
use graph_craft::proto::{NodeConstructor, TypeErasedPinned}; use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graphene_core::ops::IdNode; use graphene_core::ops::IdNode;
use graphene_core::quantization::QuantizationChannels; use graphene_core::quantization::QuantizationChannels;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
use graphene_core::structural::Then; use graphene_core::structural::Then;
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
@ -14,8 +14,7 @@ use graphene_core::{fn_type, raster::*};
use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor}; use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor};
use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_core::{Node, NodeIO, NodeIOTypes};
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::brush;
use graphene_std::raster::BlendImageTupleNode;
use graphene_std::raster::*; use graphene_std::raster::*;
use dyn_any::StaticType; use dyn_any::StaticType;
@ -51,7 +50,7 @@ macro_rules! register_node {
let node = construct_node!(args, $path, [$($type),*]).await; let node = construct_node!(args, $path, [$($type),*]).await;
let node = graphene_std::any::FutureWrapperNode::new(node); let node = graphene_std::any::FutureWrapperNode::new(node);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned Box::new(any) as TypeErasedBox
}) })
}, },
{ {
@ -79,7 +78,7 @@ macro_rules! async_node {
args.reverse(); args.reverse();
let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"))),*); let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
{ {
@ -118,7 +117,7 @@ macro_rules! raster_node {
let node = construct_node!(args, $path, [$($type),*]).await; let node = construct_node!(args, $path, [$($type),*]).await;
let node = graphene_std::any::FutureWrapperNode::new(node); let node = graphene_std::any::FutureWrapperNode::new(node);
let any: DynAnyNode<Color, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode<Color, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
{ {
@ -134,7 +133,7 @@ macro_rules! raster_node {
let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));
let map_node = graphene_std::any::FutureWrapperNode::new(map_node); let map_node = graphene_std::any::FutureWrapperNode::new(map_node);
let any: DynAnyNode<Image<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); let any: DynAnyNode<Image<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
{ {
@ -150,7 +149,7 @@ macro_rules! raster_node {
let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));
let map_node = graphene_std::any::FutureWrapperNode::new(map_node); let map_node = graphene_std::any::FutureWrapperNode::new(map_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
{ {
@ -170,7 +169,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
//register_node!(graphene_core::ops::IdNode, input: Any<'_>, params: []), //register_node!(graphene_core::ops::IdNode, input: Any<'_>, params: []),
vec![( vec![(
NodeIdentifier::new("graphene_core::ops::IdNode"), NodeIdentifier::new("graphene_core::ops::IdNode"),
|_| Box::pin(async move { Box::pin(FutureWrapperNode::new(IdNode::new())) as TypeErasedPinned }), |_| Box::pin(async move { FutureWrapperNode::new(IdNode::new()).into_type_erased() }),
NodeIOTypes::new(generic!(I), generic!(I), vec![]), NodeIOTypes::new(generic!(I), generic!(I), vec![]),
)], )],
// TODO: create macro to impl for all types // TODO: create macro to impl for all types
@ -205,10 +204,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
use graphene_core::raster::*; use graphene_core::raster::*;
use graphene_core::value::*; use graphene_core::value::*;
let channel_r: ImageFrame<Color> = DowncastBothNode::new(args[0]).eval(()).await; let channel_r: ImageFrame<Color> = DowncastBothNode::new(args[0].clone()).eval(()).await;
let channel_g: ImageFrame<Color> = DowncastBothNode::new(args[1]).eval(()).await; let channel_g: ImageFrame<Color> = DowncastBothNode::new(args[1].clone()).eval(()).await;
let channel_b: ImageFrame<Color> = DowncastBothNode::new(args[2]).eval(()).await; let channel_b: ImageFrame<Color> = DowncastBothNode::new(args[2].clone()).eval(()).await;
let channel_a: ImageFrame<Color> = DowncastBothNode::new(args[3]).eval(()).await; let channel_a: ImageFrame<Color> = DowncastBothNode::new(args[3].clone()).eval(()).await;
let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red)); let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red));
let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green)); let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green));
@ -232,7 +231,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
let final_image = FutureWrapperNode::new(final_image); let final_image = FutureWrapperNode::new(final_image);
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image)); let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
NodeIOTypes::new( NodeIOTypes::new(
@ -256,11 +255,11 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"), NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"),
|args| { |args| {
Box::pin(async move { Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0]); let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
//let document_node = ClonedNode::new(document_node.eval(())); //let document_node = ClonedNode::new(document_node.eval(()));
let node = graphene_std::executor::MapGpuNode::new(document_node); let node = graphene_std::executor::MapGpuNode::new(document_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(graph_craft::document::DocumentNode)]), NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(graph_craft::document::DocumentNode)]),
@ -270,13 +269,13 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_std::executor::BlendGpuImageNode<_, _, _>"), NodeIdentifier::new("graphene_std::executor::BlendGpuImageNode<_, _, _>"),
|args| { |args| {
Box::pin(async move { Box::pin(async move {
let background: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]); let background: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2]); let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2].clone());
let node = graphene_std::executor::BlendGpuImageNode::new(background, blend_mode, opacity); let node = graphene_std::executor::BlendGpuImageNode::new(background, blend_mode, opacity);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
NodeIOTypes::new( NodeIOTypes::new(
@ -289,7 +288,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
|args| { |args| {
Box::pin(async move { Box::pin(async move {
let node = ComposeTypeErased::new(args[0], args[1]); let node = ComposeTypeErased::new(args[0].clone(), args[1].clone());
node.into_type_erased() node.into_type_erased()
}) })
}, },
@ -300,99 +299,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
), ),
)], )],
register_node!(graphene_std::brush::IntoIterNode<_>, input: &Vec<BrushStroke>, params: []), register_node!(graphene_std::brush::IntoIterNode<_>, input: &Vec<BrushStroke>, params: []),
vec![( async_node!(graphene_std::brush::BrushNode<_, _>, input: ImageFrame<Color>, output: ImageFrame<Color>, params: [ImageFrame<Color>, Vec<BrushStroke>]),
NodeIdentifier::new("graphene_std::brush::BrushNode"),
|args| {
use graphene_core::structural::*;
use graphene_core::value::*;
use graphene_std::brush::*;
Box::pin(async move {
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
let bounds: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[1]);
let strokes: DowncastBothNode<(), Vec<BrushStroke>> = DowncastBothNode::new(args[2]);
let image_val = image.eval(()).await;
let strokes_val = strokes.eval(()).await;
let stroke_bbox = strokes_val.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let image_bbox = Bbox::from_transform(image_val.transform).to_axis_aligned_bbox();
let bbox = stroke_bbox.union(&image_bbox);
let mut background_bounds = CopiedNode::new(bbox.to_transform());
let bounds_transform = bounds.eval(()).await.transform;
if bounds_transform != DAffine2::ZERO {
background_bounds = CopiedNode::new(bounds_transform);
}
let has_erase_strokes = strokes_val.iter().any(|s| s.style.blend_mode == BlendMode::Erase);
let blank_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)));
let opaque_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::WHITE)));
let mut erase_restore_mask = has_erase_strokes.then(|| opaque_image.eval(()));
let mut actual_image = ExtendImageNode::new(blank_image).eval(image_val);
for stroke in strokes_val {
let normal_blend = BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
// Create brush texture.
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
let brush_texture = brush::create_brush_texture(stroke.style.clone());
// Compute transformation from stroke texture space into layer space, and create the stroke texture.
let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();
let mut bbox = stroke.bounding_box();
bbox.start = bbox.start.floor();
bbox.end = bbox.end.floor();
let stroke_size = bbox.size() + DVec2::splat(stroke.style.diameter);
// For numerical stability we want to place the first blit point at a stable, integer offset
// in layer space.
let snap_offset = positions[0].floor() - positions[0];
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
match stroke.style.blend_mode {
BlendMode::Erase => {
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = Some(blit_node.eval(mask));
}
}
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => {
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = Some(blit_node.eval(mask));
}
}
blend_mode => {
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend));
let empty_stroke_texture = EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)).eval(stroke_to_layer);
let stroke_texture = blit_node.eval(empty_stroke_texture);
// TODO: Is this the correct way to do opacity in blending?
actual_image = brush::blend_with_mode(actual_image, stroke_texture, blend_mode, stroke.style.color.a() * 100.);
}
}
}
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.));
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
actual_image = blend_executor.eval((actual_image, mask));
}
// TODO: there *has* to be a better way to do this.
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(FutureWrapperNode::new(ClonedNode::new(actual_image))));
Box::pin(any) as TypeErasedPinned
})
},
NodeIOTypes::new(
concrete!(()),
concrete!(ImageFrame<Color>),
vec![fn_type!(ImageFrame<Color>), fn_type!(ImageFrame<Color>), fn_type!(Vec<BrushStroke>)],
),
)],
// Filters // Filters
raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]), raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]),
raster_node!(graphene_core::raster::ExtractChannelNode<_>, params: [RedGreenBlue]), raster_node!(graphene_core::raster::ExtractChannelNode<_>, params: [RedGreenBlue]),
@ -404,13 +311,13 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"), NodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"),
|args| { |args| {
Box::pin(async move { Box::pin(async move {
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]); let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]); let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2].clone());
let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await)); let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await));
let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node))); let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node)));
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
NodeIOTypes::new( NodeIOTypes::new(
@ -438,24 +345,24 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
Box::pin(async move { Box::pin(async move {
use graphene_core::raster::brightness_contrast::*; use graphene_core::raster::brightness_contrast::*;
let brightness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[0]); let brightness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[0].clone());
let brightness = ClonedNode::new(brightness.eval(()).await as f32); let brightness = ClonedNode::new(brightness.eval(()).await as f32);
let contrast: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1]); let contrast: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1].clone());
let contrast = ClonedNode::new(contrast.eval(()).await as f32); let contrast = ClonedNode::new(contrast.eval(()).await as f32);
let use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2]); let use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2].clone());
if use_legacy.eval(()).await { if use_legacy.eval(()).await {
let generate_brightness_contrast_legacy_mapper_node = GenerateBrightnessContrastLegacyMapperNode::new(brightness, contrast); let generate_brightness_contrast_legacy_mapper_node = GenerateBrightnessContrastLegacyMapperNode::new(brightness, contrast);
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_legacy_mapper_node.eval(()))); let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_legacy_mapper_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node)); let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
} else { } else {
let generate_brightness_contrast_mapper_node = GenerateBrightnessContrastMapperNode::new(brightness, contrast); let generate_brightness_contrast_mapper_node = GenerateBrightnessContrastMapperNode::new(brightness, contrast);
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(()))); let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node)); let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
} }
}) })
}, },
@ -495,10 +402,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_core::memo::RefNode<_, _>"), NodeIdentifier::new("graphene_core::memo::RefNode<_, _>"),
|args| { |args| {
Box::pin(async move { Box::pin(async move {
let node: DowncastBothNode<Option<graphene_core::EditorApi>, graphene_core::EditorApi> = graphene_std::any::DowncastBothNode::new(args[0]); let node: DowncastBothNode<Option<graphene_core::EditorApi>, graphene_core::EditorApi> = graphene_std::any::DowncastBothNode::new(args[0].clone());
let node = <graphene_core::memo::RefNode<_, _>>::new(node); let node = <graphene_core::memo::RefNode<_, _>>::new(node);
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned any.into_type_erased()
}) })
}, },
NodeIOTypes::new( NodeIOTypes::new(

View File

@ -2,7 +2,7 @@ use std::error::Error;
use super::context::Context; use super::context::Context;
use graph_craft::executor::Executor; use graph_craft::graphene_compiler::Executor;
use graph_craft::proto::LocalFuture; use graph_craft::proto::LocalFuture;
use graphene_core::gpu::PushConstants; use graphene_core::gpu::PushConstants;

View File

@ -5,7 +5,7 @@ use wgpu::util::DeviceExt;
use super::context::Context; use super::context::Context;
use bytemuck::Pod; use bytemuck::Pod;
use dyn_any::StaticTypeSized; use dyn_any::StaticTypeSized;
use graph_craft::{executor::Executor, proto::LocalFuture}; use graph_craft::{graphene_compiler::Executor, proto::LocalFuture};
#[derive(Debug)] #[derive(Debug)]
pub struct GpuExecutor<'a, I: StaticTypeSized, O> { pub struct GpuExecutor<'a, I: StaticTypeSized, O> {