Skip to content

Commit

Permalink
Wrap output type of all nodes in future
Browse files Browse the repository at this point in the history
  • Loading branch information
TrueDoctor authored and Keavon committed Feb 3, 2025
1 parent 2aad15a commit de52885
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 79 deletions.
1 change: 1 addition & 0 deletions node-graph/gcore/src/generic.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use core::marker::PhantomData;

use crate::Node;
#[derive(Clone)]
pub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);

impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {
Expand Down
2 changes: 1 addition & 1 deletion node-graph/gcore/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn log_to_console<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bo
value
}

#[node_macro::node(category("Debug"))]
#[node_macro::node(category("Debug"), skip_impl)]
fn to_string<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
format!("{:?}", value)
}
Expand Down
2 changes: 1 addition & 1 deletion node-graph/gcore/src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
// TODO: Rename to "Passthrough"
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
#[node_macro::node(skip_impl)]
fn identity<'i, T: 'i>(value: T) -> T {
fn identity<'i, T: 'i + Send>(value: T) -> T {
value
}

Expand Down
80 changes: 54 additions & 26 deletions node-graph/gstd/src/brush.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, ExtendImageToBoundsNode};

use graph_craft::generic::FnNode;
use graph_craft::proto::FutureWrapperNode;
use graphene_core::raster::adjustments::blend_colors;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::brush_cache::BrushCache;
Expand Down Expand Up @@ -131,14 +133,21 @@ where
target
}

pub fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
let blank_texture = EmptyImageNode::new(CopiedNode::new(transform), CopiedNode::new(Color::TRANSPARENT)).eval(());
let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.)));
use crate::raster::empty_image;
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
let normal_blend = BlendColorPairNode::new(
FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))),
FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))),
);
normal_blend.eval((Color::default(), Color::default()));
let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend));
blend_executor.eval((blank_texture, stamp)).image
// use crate::raster::blend_image_tuple;
// blend_image_tuple((blank_texture, stamp), &normal_blend).await.image;
crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 100.)).image
// let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend)));
// blend_executor.eval((blank_texture, stamp)).image
}

macro_rules! inline_blend_funcs {
Expand Down Expand Up @@ -202,7 +211,7 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
}

#[node_macro::node(category(""))]
fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
let image = image.one_item().clone();

let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
Expand All @@ -225,11 +234,13 @@ fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Col
for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {
// Create brush texture.
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
let brush_texture = cache.get_cached_brush(&stroke.style).unwrap_or_else(|| {
let tex = create_brush_texture(&stroke.style);
let mut brush_texture = cache.get_cached_brush(&stroke.style);
if brush_texture.is_none() {
let tex = create_brush_texture(&stroke.style).await;
cache.store_brush(stroke.style.clone(), tex.clone());
tex
});
brush_texture = Some(tex);
}
let brush_texture = brush_texture.unwrap();

// Compute transformation from stroke texture space into layer space, and create the stroke texture.
let skip = if idx == 0 { brush_plan.first_stroke_point_skip } else { 0 };
Expand All @@ -247,15 +258,22 @@ fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Col
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);

let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.)));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend));
let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 100.));
let blit_node = BlitNode::new(
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(normal_blend)),
);
let blit_target = if idx == 0 {
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
ExtendImageToBoundsNode::new(CopiedNode::new(stroke_to_layer)).eval(target)
} else {
EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
use crate::raster::empty_image;
empty_image((), stroke_to_layer, Color::TRANSPARENT)
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
};

blit_node.eval(blit_target)
blit_node.eval(blit_target).await
};

// Cache image before doing final blend, and store final stroke texture.
Expand All @@ -277,34 +295,44 @@ fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Col
let mut erase_restore_mask = opaque_image;

for stroke in erase_restore_strokes {
let brush_texture = cache.get_cached_brush(&stroke.style).unwrap_or_else(|| {
let tex = create_brush_texture(&stroke.style);
let mut brush_texture = cache.get_cached_brush(&stroke.style);
if brush_texture.is_none() {
let tex = create_brush_texture(&stroke.style).await;
cache.store_brush(stroke.style.clone(), tex.clone());
tex
});
brush_texture = Some(tex);
}
let brush_texture = brush_texture.unwrap();
let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();

match stroke.style.blend_mode {
BlendMode::Erase => {
let blend_params = BlendColorPairNode::new(ValueNode(CopiedNode::new(BlendMode::Erase)), ValueNode(CopiedNode::new(100.)));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = blit_node.eval(erase_restore_mask);
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 100.));
let blit_node = BlitNode::new(
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
}

// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => {
let blend_params = BlendColorPairNode::new(ValueNode(CopiedNode::new(BlendMode::Restore)), ValueNode(CopiedNode::new(100.)));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = blit_node.eval(erase_restore_mask);
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 100.));
let blit_node = BlitNode::new(
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
}

_ => unreachable!(),
}
}

let blend_params = BlendColorPairNode::new(ValueNode(CopiedNode::new(BlendMode::MultiplyAlpha)), ValueNode(CopiedNode::new(100.)));
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
actual_image = blend_executor.eval((actual_image, erase_restore_mask));
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 100.));
let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params)));
actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await;
}

ImageFrameTable::new(actual_image)
Expand Down
22 changes: 11 additions & 11 deletions node-graph/gstd/src/raster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl From<std::io::Error> for Error {
}

#[node_macro::node(category("Debug: Raster"))]
fn sample_image(ctx: impl ExtractFootprint + Clone, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
let image_frame = image_frame.one_item();

// Resize the image using the image crate
Expand Down Expand Up @@ -248,17 +248,17 @@ fn mask_image<
image
}

#[derive(Debug, Clone, Copy)]
pub struct BlendImageTupleNode<P, Fg, MapFn> {
map_fn: MapFn,
_p: PhantomData<P>,
_fg: PhantomData<Fg>,
}
// #[derive(Debug, Clone, Copy)]
// pub struct BlendImageTupleNode<P, Fg, MapFn> {
// map_fn: MapFn,
// _p: PhantomData<P>,
// _fg: PhantomData<Fg>,
// }

#[node_macro::old_node_fn(BlendImageTupleNode<_P, _Fg>)]
fn blend_image_tuple<_P: Alpha + Pixel + Debug, MapFn, _Fg: Sample<Pixel = _P> + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'input MapFn) -> ImageFrame<_P>
#[node_macro::node(skip_impl)]
async fn blend_image_tuple<_P: Alpha + Pixel + Debug + Send, MapFn, _Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n>(images: (ImageFrame<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrame<_P>
where
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone,
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
{
let (background, foreground) = images;

Expand Down Expand Up @@ -628,7 +628,7 @@ fn noise_pattern(
}

#[node_macro::node(category("Raster: Generator"))]
fn mandelbrot(ctx: impl ExtractFootprint) -> ImageFrameTable<Color> {
fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
let footprint = ctx.footprint();
let viewport_bounds = footprint.viewport_bounds_in_local_space();

Expand Down
75 changes: 47 additions & 28 deletions node-graph/interpreted-executor/src/node_registry.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use dyn_any::StaticType;
use dyn_any::{DynFuture, StaticType};
use graph_craft::document::value::RenderOutput;
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graphene_core::fn_type;
Expand Down Expand Up @@ -32,6 +32,7 @@ macro_rules! construct_node {
let node = <$path>::new($(
{
let node = graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"));
// let node = graphene_std::any::FutureWrapperNode::new(node);
let value = node.eval(None).await;
graphene_core::value::ClonedNode::new(value)
}
Expand All @@ -51,7 +52,6 @@ macro_rules! register_node {
|args| {
Box::pin(async move {
let node = construct_node!(args, $path, [$($arg => $type),*]).await;
let node = graphene_std::any::FutureWrapperNode::new(node);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
Expand Down Expand Up @@ -105,11 +105,11 @@ macro_rules! async_node {
// TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
(
ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
NodeIOTypes::new(generic!(I), generic!(I), vec![]),
),
// (
// ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
// |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
// NodeIOTypes::new(generic!(I), generic!(I), vec![]),
// ),
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<SRGBA8>>, input: ImageFrameTable<Color>, params: []),
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<Color>>, input: ImageFrameTable<SRGBA8>, params: []),
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
Expand All @@ -128,7 +128,26 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]),
#[cfg(feature = "gpu")]
register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: Context, params: [&WasmEditorApi]),
(
ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)),
|args| {
Box::pin(async move {
let editor_api: DowncastBothNode<Context, &WasmEditorApi> = DowncastBothNode::new(args[1].clone());
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
// let node = FutureWrapperNode::new(node);
let any: DynAnyNode<Context, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, DynFuture<'static, &WasmEditorApi>>::new());
let params = vec![fn_type!(Context, DynFuture<'static, &WasmEditorApi>)];
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<Context as StaticType>::Static);
node_io
},
),
// register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: Context, params: [&WasmEditorApi]),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"),
Expand Down Expand Up @@ -213,27 +232,27 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
// NodeIOTypes::new(concrete!(ImageFrameTable<Luma>), concrete!(ImageFrameTable<Luma>), vec![fn_type!(graphene_core::raster::curve::Curve)]),
// ),
// TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color.
(
ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
|args| {
use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
Box::pin(async move {
let curve = ClonedNode::new(curve.eval(()).await);
// (
// ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"),
// |args| {
// use graphene_core::raster::{curve::Curve, GenerateCurvesNode};
// let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone());
// Box::pin(async move {
// let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await));

let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32));
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
any.into_type_erased()
})
},
NodeIOTypes::new(
concrete!(ImageFrameTable<Color>),
concrete!(ImageFrameTable<Color>),
vec![fn_type!(graphene_core::raster::curve::Curve)],
),
),
// let generate_curves_node = GenerateCurvesNode::new(FutureWrapperNode::new(curve), FutureWrapperNode::new(ClonedNode::new(0_f32)));
// let map_image_frame_node = graphene_std::raster::MapImageNode::new(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(()))));
// let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
// let any: DynAnyNode<ImageFrameTable<Color>, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node);
// any.into_type_erased()
// })
// },
// NodeIOTypes::new(
// concrete!(ImageFrameTable<Color>),
// concrete!(ImageFrameTable<Color>),
// vec![fn_type!(graphene_core::raster::curve::Curve)],
// ),
// ),
// (
// ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"),
// |args: Vec<graph_craft::proto::SharedNodeContainer>| {
Expand Down
Loading

0 comments on commit de52885

Please sign in to comment.