mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
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:
parent
5558deba5e
commit
26473a8002
29 changed files with 363 additions and 299 deletions
|
@ -14,7 +14,7 @@ fn main() {
|
|||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
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 io = ShaderIO {
|
||||
|
|
|
@ -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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
#[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;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
|
@ -130,7 +131,7 @@ use core::pin::Pin;
|
|||
|
||||
use dyn_any::StaticTypeSized;
|
||||
#[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;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
|
|
|
@ -5,8 +5,9 @@ edition = "2021"
|
|||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["dealloc_nodes"]
|
||||
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
|
||||
|
||||
|
|
|
@ -892,7 +892,7 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{cell::Cell, sync::atomic::AtomicU64};
|
||||
use std::sync::atomic::AtomicU64;
|
||||
|
||||
use super::*;
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
||||
|
@ -1193,7 +1193,7 @@ mod test {
|
|||
.collect(),
|
||||
..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(2, |self_id, inner_id| self_id * 10 + inner_id, || 10001);
|
||||
network.remove_dead_nodes();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::DocumentNode;
|
||||
use crate::executor::Any;
|
||||
use crate::graphene_compiler::Any;
|
||||
pub use crate::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus};
|
||||
use crate::proto::{Any as DAny, FutureAny};
|
||||
|
||||
|
|
|
@ -8,5 +8,5 @@ pub use graphene_core::{concrete, generic, NodeIdentifier, Type, TypeDescriptor}
|
|||
pub mod document;
|
||||
pub mod proto;
|
||||
|
||||
pub mod executor;
|
||||
pub mod graphene_compiler;
|
||||
pub mod imaginate_input;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use std::hash::Hash;
|
||||
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 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 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 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))]
|
||||
#[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.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct TypingContext {
|
||||
lookup: Cow<'static, HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>,
|
||||
inferred: HashMap<NodeId, NodeIOTypes>,
|
||||
|
@ -539,7 +592,7 @@ impl TypingContext {
|
|||
dbg!(&self.inferred);
|
||||
Err(format!(
|
||||
"No implementations found for {identifier} with \ninput: {input:?} and \nparameters: {parameters:?}.\nOther Implementations found: {:?}",
|
||||
impls,
|
||||
impls.keys().collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
[(org_nio, output)] => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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 graphene_core::NodeIO;
|
||||
pub use graphene_core::{generic, ops, Node};
|
||||
use std::marker::PhantomData;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
pub struct DynAnyNode<I, O, Node> {
|
||||
node: Node,
|
||||
|
@ -124,15 +124,15 @@ impl<'i, N> FutureWrapperNode<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
|
||||
where
|
||||
N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n,
|
||||
{
|
||||
fn into_type_erased(self) -> TypeErasedPinned<'n> {
|
||||
Box::pin(self)
|
||||
fn into_type_erased(self) -> TypeErasedBox<'n> {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,13 +159,13 @@ where
|
|||
|
||||
/// Boxes the input and downcasts the output.
|
||||
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct DowncastBothNode<'a, I, O> {
|
||||
node: TypeErasedPinnedRef<'a>,
|
||||
#[derive(Clone)]
|
||||
pub struct DowncastBothNode<I, O> {
|
||||
node: Arc<NodeContainer>,
|
||||
_i: PhantomData<I>,
|
||||
_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>;
|
||||
#[inline]
|
||||
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> {
|
||||
pub const fn new(node: TypeErasedPinnedRef<'n>) -> Self {
|
||||
impl<I, O> DowncastBothNode<I, O> {
|
||||
pub const fn new(node: Arc<NodeContainer>) -> Self {
|
||||
Self {
|
||||
node,
|
||||
_i: core::marker::PhantomData,
|
||||
|
@ -191,12 +191,12 @@ impl<'n, I, O> DowncastBothNode<'n, I, O> {
|
|||
}
|
||||
/// Boxes the input and downcasts the output.
|
||||
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct DowncastBothRefNode<'a, I, O> {
|
||||
node: TypeErasedPinnedRef<'a>,
|
||||
#[derive(Clone)]
|
||||
pub struct DowncastBothRefNode<I, O> {
|
||||
node: Arc<NodeContainer>,
|
||||
_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>;
|
||||
#[inline]
|
||||
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> {
|
||||
pub const fn new(node: TypeErasedPinnedRef<'n>) -> Self {
|
||||
impl<I, O> DowncastBothRefNode<I, O> {
|
||||
pub const fn new(node: Arc<NodeContainer>) -> Self {
|
||||
Self { node, _i: core::marker::PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ComposeTypeErased<'a> {
|
||||
first: TypeErasedPinnedRef<'a>,
|
||||
second: TypeErasedPinnedRef<'a>,
|
||||
pub struct ComposeTypeErased {
|
||||
first: Arc<NodeContainer>,
|
||||
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>>;
|
||||
fn eval(&'i self, input: Any<'i>) -> Self::Output {
|
||||
Box::pin(async move {
|
||||
|
@ -231,13 +231,13 @@ impl<'i, 'a: 'i> Node<'i, Any<'i>> for ComposeTypeErased<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> ComposeTypeErased<'a> {
|
||||
pub const fn new(first: TypeErasedPinnedRef<'a>, second: TypeErasedPinnedRef<'a>) -> Self {
|
||||
impl ComposeTypeErased {
|
||||
pub const fn new(first: Arc<NodeContainer>, second: Arc<NodeContainer>) -> Self {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -267,22 +267,23 @@ mod test {
|
|||
//let add = DynAnyNode::new(AddNode::new()).into_type_erased();
|
||||
//add.eval(Box::new(&("32", 32u32)));
|
||||
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 type_erased = Box::pin(dyn_any) as TypeErasedPinned<'_>;
|
||||
//let type_erased = Box::pin(dyn_any) as TypeErasedBox<'_>;
|
||||
type_erased.eval(Box::new(&("32", 32u32)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn dyn_input_invalid_eval_panic_() {
|
||||
pub fn dyn_input_compose() {
|
||||
//let add = DynAnyNode::new(AddNode::new()).into_type_erased();
|
||||
//add.eval(Box::new(&("32", 32u32)));
|
||||
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)));
|
||||
let id_node = FutureWrapperNode::new(IdNode::new());
|
||||
let type_erased_id = Box::pin(id_node) as TypeErasedPinned;
|
||||
let type_erased = ComposeTypeErased::new(type_erased.as_ref(), type_erased_id.as_ref());
|
||||
let any_id = DynAnyNode::<u32, u32, _>::new(ValueNode::new(id_node));
|
||||
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)));
|
||||
//let downcast: DowncastBothNode<(u32, u32), u32> = DowncastBothNode::new(type_erased.as_ref());
|
||||
//downcast.eval((4u32, 2u32));
|
||||
|
|
|
@ -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::bbox::{AxisAlignedBbox, Bbox};
|
||||
use graphene_core::raster::{Alpha, Color, Image, ImageFrame, Pixel, Sample};
|
||||
use graphene_core::raster::{BlendMode, BlendNode};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::value::{CopiedNode, ValueNode};
|
||||
use graphene_core::vector::brush_stroke::BrushStyle;
|
||||
use graphene_core::value::{ClonedNode, CopiedNode, OnceCellNode, ValueNode};
|
||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use graphene_core::vector::VectorData;
|
||||
use graphene_core::Node;
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -18,7 +18,7 @@ pub struct GpuCompiler<TypingContext, ShaderIO> {
|
|||
// TODO: Move to graph-craft
|
||||
#[node_macro::node_fn(GpuCompiler)]
|
||||
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 proto_networks: Vec<_> = compiler.compile(network.clone(), true).collect();
|
||||
|
||||
|
@ -44,7 +44,7 @@ pub struct MapGpuNode<Node> {
|
|||
#[node_macro::node_fn(MapGpuNode)]
|
||||
async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Color> {
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
log::debug!("Executing gpu blend node!");
|
||||
let compiler = graph_craft::executor::Compiler {};
|
||||
let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
|
||||
let network = NodeNetwork {
|
||||
inputs: vec![],
|
||||
|
|
|
@ -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>>(
|
||||
foreground: Frame,
|
||||
mut background: Background,
|
||||
background: Background,
|
||||
map_fn: &MapFn,
|
||||
) -> Background
|
||||
where
|
||||
|
|
|
@ -27,3 +27,4 @@ serde = { version = "1", features = ["derive"], optional = true }
|
|||
glam = { version = "0.22" }
|
||||
once_cell = "1.17.0"
|
||||
futures = "0.3.28"
|
||||
typed-arena = "2.0.2"
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use dyn_any::StaticType;
|
||||
use graph_craft::document::value::{TaggedValue, UpcastNode};
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::executor::Executor;
|
||||
use graph_craft::proto::{ConstructionArgs, LocalFuture, ProtoNetwork, ProtoNode, TypingContext};
|
||||
use graph_craft::graphene_compiler::Executor;
|
||||
use graph_craft::proto::{ConstructionArgs, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, TypeErasedBox, TypingContext};
|
||||
use graph_craft::Type;
|
||||
use graphene_std::any::{TypeErasedPinned, TypeErasedPinnedRef};
|
||||
|
||||
use crate::node_registry;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DynamicExecutor {
|
||||
output: NodeId,
|
||||
tree: BorrowTree,
|
||||
|
@ -79,45 +78,14 @@ impl<'a, I: StaticType + 'a> Executor<I, TaggedValue> for &'a DynamicExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct NodeContainer<'n> {
|
||||
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)]
|
||||
#[derive(Default)]
|
||||
pub struct BorrowTree {
|
||||
nodes: HashMap<NodeId, Arc<RwLock<NodeContainer<'static>>>>,
|
||||
nodes: HashMap<NodeId, Arc<NodeContainer>>,
|
||||
source_map: HashMap<Vec<NodeId>, NodeId>,
|
||||
}
|
||||
|
||||
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();
|
||||
for (id, node) in proto_network.nodes {
|
||||
nodes.push_node(id, node, typing_context).await?
|
||||
|
@ -133,9 +101,7 @@ impl BorrowTree {
|
|||
self.push_node(id, node, typing_context).await?;
|
||||
} else {
|
||||
let Some(node_container) = self.nodes.get_mut(&id) else { continue };
|
||||
let mut node_container_writer = node_container.write().unwrap();
|
||||
let node = node_container_writer.node.as_mut();
|
||||
node.reset();
|
||||
node_container.reset();
|
||||
}
|
||||
old_nodes.remove(&id);
|
||||
}
|
||||
|
@ -143,40 +109,32 @@ impl BorrowTree {
|
|||
Ok(old_nodes.into_iter().collect())
|
||||
}
|
||||
|
||||
fn node_refs(&self, nodes: &[NodeId]) -> Vec<TypeErasedPinnedRef<'static>> {
|
||||
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>>>> {
|
||||
fn node_deps(&self, nodes: &[NodeId]) -> Vec<Arc<NodeContainer>> {
|
||||
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>>> {
|
||||
self.nodes.insert(id, node.clone());
|
||||
node
|
||||
fn store_node(&mut self, node: Arc<NodeContainer>, id: NodeId) {
|
||||
self.nodes.insert(id, node);
|
||||
}
|
||||
|
||||
pub fn introspect(&self, node_path: &[NodeId]) -> Option<Option<Arc<dyn std::any::Any>>> {
|
||||
let id = self.source_map.get(node_path)?;
|
||||
let node = self.nodes.get(id)?;
|
||||
let reader = node.read().unwrap();
|
||||
let node = reader.node.as_ref();
|
||||
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()
|
||||
}
|
||||
|
||||
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 reader = node.read().unwrap();
|
||||
let output = reader.node.eval(Box::new(input));
|
||||
let output = node.eval(Box::new(input));
|
||||
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> {
|
||||
let node = self.nodes.get(&id).cloned().ok_or_else(|| "Output node not found in executor")?;
|
||||
let reader = node.read().unwrap();
|
||||
let output = reader.node.eval(Box::new(input));
|
||||
let node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
|
||||
let output = node.eval(Box::new(input));
|
||||
TaggedValue::try_from_any(output.await)
|
||||
}
|
||||
|
||||
|
@ -196,23 +154,18 @@ impl BorrowTree {
|
|||
match construction_args {
|
||||
ConstructionArgs::Value(value) => {
|
||||
let upcasted = UpcastNode::new(value);
|
||||
let node = Box::pin(upcasted) as TypeErasedPinned<'_>;
|
||||
let node = NodeContainer { node, _dependencies: vec![] };
|
||||
let node = unsafe { node.erase_lifetime() };
|
||||
self.store_node(Arc::new(node.into()), id);
|
||||
let node = Box::new(upcasted) as TypeErasedBox<'_>;
|
||||
let node = NodeContainer::new(node);
|
||||
self.store_node(node.into(), id);
|
||||
}
|
||||
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
|
||||
ConstructionArgs::Nodes(ids) => {
|
||||
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 node = constructor(construction_nodes).await;
|
||||
let node = NodeContainer {
|
||||
node,
|
||||
_dependencies: self.node_deps(&ids),
|
||||
};
|
||||
let node = unsafe { node.erase_lifetime() };
|
||||
self.store_node(Arc::new(node.into()), id);
|
||||
let node = NodeContainer::new(node);
|
||||
self.store_node(node.into(), id);
|
||||
}
|
||||
};
|
||||
Ok(())
|
|
@ -1,7 +1,4 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod executor;
|
||||
pub mod dynamic_executor;
|
||||
pub mod node_registry;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -72,8 +69,8 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
use crate::executor::DynamicExecutor;
|
||||
use graph_craft::executor::{Compiler, Executor};
|
||||
use crate::dynamic_executor::DynamicExecutor;
|
||||
use graph_craft::graphene_compiler::{Compiler, Executor};
|
||||
|
||||
let compiler = Compiler {};
|
||||
let protograph = compiler.compile_single(network, true).expect("Graph should be generated");
|
||||
|
@ -120,12 +117,12 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
use crate::executor::DynamicExecutor;
|
||||
use graph_craft::executor::Compiler;
|
||||
use crate::dynamic_executor::DynamicExecutor;
|
||||
use graph_craft::graphene_compiler::Compiler;
|
||||
|
||||
let compiler = Compiler {};
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use graph_craft::proto::{NodeConstructor, TypeErasedPinned};
|
||||
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
||||
use graphene_core::ops::IdNode;
|
||||
use graphene_core::quantization::QuantizationChannels;
|
||||
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
|
||||
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::structural::Then;
|
||||
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::{Node, NodeIO, NodeIOTypes};
|
||||
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
|
||||
use graphene_std::brush;
|
||||
use graphene_std::raster::BlendImageTupleNode;
|
||||
|
||||
use graphene_std::raster::*;
|
||||
|
||||
use dyn_any::StaticType;
|
||||
|
@ -51,7 +50,7 @@ macro_rules! register_node {
|
|||
let node = construct_node!(args, $path, [$($type),*]).await;
|
||||
let node = graphene_std::any::FutureWrapperNode::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();
|
||||
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));
|
||||
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 = graphene_std::any::FutureWrapperNode::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::any::FutureWrapperNode::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::any::FutureWrapperNode::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: []),
|
||||
vec![(
|
||||
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![]),
|
||||
)],
|
||||
// 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::value::*;
|
||||
|
||||
let channel_r: ImageFrame<Color> = DowncastBothNode::new(args[0]).eval(()).await;
|
||||
let channel_g: ImageFrame<Color> = DowncastBothNode::new(args[1]).eval(()).await;
|
||||
let channel_b: ImageFrame<Color> = DowncastBothNode::new(args[2]).eval(()).await;
|
||||
let channel_a: ImageFrame<Color> = DowncastBothNode::new(args[3]).eval(()).await;
|
||||
let channel_r: ImageFrame<Color> = DowncastBothNode::new(args[0].clone()).eval(()).await;
|
||||
let channel_g: ImageFrame<Color> = DowncastBothNode::new(args[1].clone()).eval(()).await;
|
||||
let channel_b: ImageFrame<Color> = DowncastBothNode::new(args[2].clone()).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_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 any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image));
|
||||
Box::pin(any) as TypeErasedPinned
|
||||
any.into_type_erased()
|
||||
})
|
||||
},
|
||||
NodeIOTypes::new(
|
||||
|
@ -256,11 +255,11 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"),
|
||||
|args| {
|
||||
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 node = graphene_std::executor::MapGpuNode::new(document_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)]),
|
||||
|
@ -270,13 +269,13 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
NodeIdentifier::new("graphene_std::executor::BlendGpuImageNode<_, _, _>"),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let background: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
|
||||
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
|
||||
let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2]);
|
||||
let background: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
|
||||
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
|
||||
let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2].clone());
|
||||
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));
|
||||
|
||||
Box::pin(any) as TypeErasedPinned
|
||||
any.into_type_erased()
|
||||
})
|
||||
},
|
||||
NodeIOTypes::new(
|
||||
|
@ -289,7 +288,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
|
||||
|args| {
|
||||
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()
|
||||
})
|
||||
},
|
||||
|
@ -300,99 +299,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
),
|
||||
)],
|
||||
register_node!(graphene_std::brush::IntoIterNode<_>, input: &Vec<BrushStroke>, params: []),
|
||||
vec![(
|
||||
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>)],
|
||||
),
|
||||
)],
|
||||
async_node!(graphene_std::brush::BrushNode<_, _>, input: ImageFrame<Color>, output: ImageFrame<Color>, params: [ImageFrame<Color>, Vec<BrushStroke>]),
|
||||
// Filters
|
||||
raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]),
|
||||
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<_, _, _, _>"),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
|
||||
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
|
||||
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
|
||||
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
|
||||
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
|
||||
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 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));
|
||||
Box::pin(any) as TypeErasedPinned
|
||||
any.into_type_erased()
|
||||
})
|
||||
},
|
||||
NodeIOTypes::new(
|
||||
|
@ -438,24 +345,24 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
Box::pin(async move {
|
||||
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 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 use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2]);
|
||||
let use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2].clone());
|
||||
|
||||
if use_legacy.eval(()).await {
|
||||
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 = FutureWrapperNode::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 {
|
||||
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 = FutureWrapperNode::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<_, _>"),
|
||||
|args| {
|
||||
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 any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
|
||||
Box::pin(any) as TypeErasedPinned
|
||||
any.into_type_erased()
|
||||
})
|
||||
},
|
||||
NodeIOTypes::new(
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::error::Error;
|
|||
|
||||
use super::context::Context;
|
||||
|
||||
use graph_craft::executor::Executor;
|
||||
use graph_craft::graphene_compiler::Executor;
|
||||
|
||||
use graph_craft::proto::LocalFuture;
|
||||
use graphene_core::gpu::PushConstants;
|
||||
|
|
|
@ -5,7 +5,7 @@ use wgpu::util::DeviceExt;
|
|||
use super::context::Context;
|
||||
use bytemuck::Pod;
|
||||
use dyn_any::StaticTypeSized;
|
||||
use graph_craft::{executor::Executor, proto::LocalFuture};
|
||||
use graph_craft::{graphene_compiler::Executor, proto::LocalFuture};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GpuExecutor<'a, I: StaticTypeSized, O> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue