mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08: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:
committed by
Keavon Chambers
parent
5558deba5e
commit
26473a8002
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user