Use canvas as target for raster rendering (#1256)

* Implement ApplicationIo

* Simplify output duplication logic

* Fix let node initialization for ExtractImageFrame

* Async macros

* Use manual node registry impl

* Fix canvas insertion into the dom
This commit is contained in:
Dennis Kobert
2023-05-30 20:12:59 +02:00
committed by Keavon Chambers
parent 57415b948b
commit 259dcdc628
27 changed files with 810 additions and 259 deletions

View File

@@ -18,7 +18,7 @@ std = [
"num-traits/std",
"rustybuzz",
]
default = ["async", "serde", "kurbo", "log", "std", "rand_chacha"]
default = ["async", "serde", "kurbo", "log", "std", "rand_chacha", "wasm"]
log = ["dep:log"]
serde = [
"dep:serde",
@@ -32,6 +32,7 @@ async = ["async-trait", "alloc"]
nightly = []
alloc = ["dyn-any", "bezier-rs", "once_cell"]
type_id_logging = []
wasm = ["wasm-bindgen", "web-sys", "js-sys", "std"]
[dependencies]
dyn-any = { path = "../../libraries/dyn-any", features = [
@@ -68,3 +69,18 @@ num-derive = { version = "0.3.3" }
num-traits = { version = "0.2.15", default-features = false, features = [
"i128",
] }
wasm-bindgen = { version = "0.2.84", optional = true }
js-sys = { version = "0.3.55", optional = true }
[dependencies.web-sys]
version = "0.3.4"
optional = true
features = [
"Window",
"CanvasRenderingContext2d",
"ImageData",
"Document",
"HtmlCanvasElement",
]

View File

@@ -0,0 +1,159 @@
use crate::raster::ImageFrame;
use crate::transform::Transform;
use crate::transform::TransformMut;
use crate::Color;
use crate::Node;
use alloc::sync::Arc;
use dyn_any::StaticType;
use dyn_any::StaticTypeSized;
use glam::DAffine2;
use core::hash::{Hash, Hasher};
use crate::text::FontCache;
use core::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SurfaceId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SurfaceFrame {
pub surface_id: SurfaceId,
pub transform: DAffine2,
}
impl Hash for SurfaceFrame {
fn hash<H: Hasher>(&self, state: &mut H) {
self.surface_id.hash(state);
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
}
}
unsafe impl StaticType for SurfaceFrame {
type Static = SurfaceFrame;
}
#[derive(Clone)]
pub struct SurfaceHandle<'a, Surface> {
pub surface_id: SurfaceId,
pub surface: Surface,
application_io: &'a dyn ApplicationIo<Surface = Surface>,
}
unsafe impl<T: 'static> StaticType for SurfaceHandle<'_, T> {
type Static = SurfaceHandle<'static, T>;
}
#[derive(Clone)]
pub struct SurfaceHandleFrame<'a, Surface> {
pub surface_handle: Arc<SurfaceHandle<'a, Surface>>,
pub transform: DAffine2,
}
unsafe impl<T: 'static> StaticType for SurfaceHandleFrame<'_, T> {
type Static = SurfaceHandleFrame<'static, T>;
}
impl<T> Transform for SurfaceHandleFrame<'_, T> {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl<T> TransformMut for SurfaceHandleFrame<'_, T> {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
// TODO: think about how to automatically clean up memory
/*
impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {
fn drop(&mut self) {
self.application_io.destroy_surface(self.surface_id)
}
}*/
pub trait ApplicationIo {
type Surface;
fn create_surface(&self) -> SurfaceHandle<Self::Surface>;
fn destroy_surface(&self, surface_id: SurfaceId);
}
impl<T: ApplicationIo> ApplicationIo for &T {
type Surface = T::Surface;
fn create_surface(&self) -> SurfaceHandle<T::Surface> {
(**self).create_surface()
}
fn destroy_surface(&self, surface_id: SurfaceId) {
(**self).destroy_surface(surface_id)
}
}
pub struct EditorApi<'a, Io> {
pub image_frame: Option<ImageFrame<Color>>,
pub font_cache: &'a FontCache,
pub application_io: &'a Io,
}
impl<'a, Io> Clone for EditorApi<'a, Io> {
fn clone(&self) -> Self {
Self {
image_frame: self.image_frame.clone(),
font_cache: self.font_cache,
application_io: self.application_io,
}
}
}
impl<'a, T> PartialEq for EditorApi<'a, T> {
fn eq(&self, other: &Self) -> bool {
self.image_frame == other.image_frame && self.font_cache == other.font_cache
}
}
impl<'a, T> Hash for EditorApi<'a, T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.image_frame.hash(state);
self.font_cache.hash(state);
}
}
impl<'a, T> Debug for EditorApi<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EditorApi").field("image_frame", &self.image_frame).field("font_cache", &self.font_cache).finish()
}
}
unsafe impl<T: StaticTypeSized> StaticType for EditorApi<'_, T> {
type Static = EditorApi<'static, T::Static>;
}
impl<'a, T> AsRef<EditorApi<'a, T>> for EditorApi<'a, T> {
fn as_ref(&self) -> &EditorApi<'a, T> {
self
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ExtractImageFrame;
impl<'a: 'input, 'input, T> Node<'input, &'a EditorApi<'a, T>> for ExtractImageFrame {
type Output = ImageFrame<Color>;
fn eval(&'input self, editor_api: &'a EditorApi<'a, T>) -> Self::Output {
editor_api.image_frame.clone().unwrap_or(ImageFrame::identity())
}
}
impl ExtractImageFrame {
pub fn new() -> Self {
Self
}
}
#[cfg(feature = "wasm")]
pub mod wasm_application_io;

View File

@@ -0,0 +1,131 @@
use std::{cell::RefCell, collections::HashMap, sync::Mutex};
use super::{ApplicationIo, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
use crate::{
raster::{color::SRGBA8, ImageFrame, Pixel},
Node,
};
use alloc::sync::Arc;
use dyn_any::StaticType;
use js_sys::{Object, Reflect};
use wasm_bindgen::{Clamped, JsCast, JsValue};
use web_sys::{window, CanvasRenderingContext2d, HtmlCanvasElement};
pub struct Canvas(CanvasRenderingContext2d);
#[derive(Debug, Default)]
pub struct WasmApplicationIo {
ids: RefCell<u64>,
canvases: RefCell<HashMap<SurfaceId, CanvasRenderingContext2d>>,
}
impl WasmApplicationIo {
pub fn new() -> Self {
Self::default()
}
}
unsafe impl StaticType for WasmApplicationIo {
type Static = WasmApplicationIo;
}
pub type WasmEditorApi<'a> = super::EditorApi<'a, WasmApplicationIo>;
impl ApplicationIo for WasmApplicationIo {
type Surface = CanvasRenderingContext2d;
fn create_surface(&self) -> SurfaceHandle<Self::Surface> {
let mut wrapper = || {
let document = window().expect("should have a window in this context").document().expect("window should have a document");
let canvas: HtmlCanvasElement = document.create_element("canvas")?.dyn_into::<HtmlCanvasElement>()?;
// TODO: replace "2d" with "bitmaprenderer" once we switch to ImageBitmap (lives on gpu) from ImageData (lives on cpu)
let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
let mut guard = self.ids.borrow_mut();
let id = SurfaceId(*guard);
*guard += 1;
self.canvases.borrow_mut().insert(id, context.clone());
// store the canvas in the global scope so it doesn't get garbage collected
let window = window().expect("should have a window in this context");
let window = Object::from(window);
let image_canvases_key = JsValue::from_str("imageCanvases");
let mut canvases = Reflect::get(&window, &image_canvases_key);
if let Err(e) = canvases {
Reflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap();
canvases = Reflect::get(&window, &image_canvases_key);
}
// Convert key and value to JsValue
let js_key = JsValue::from_str(format!("canvas{}", id.0).as_str());
let js_value = JsValue::from(context.clone());
let canvases = Object::from(canvases.unwrap());
// Use Reflect API to set property
Reflect::set(&canvases, &js_key, &js_value)?;
Ok::<_, JsValue>(SurfaceHandle {
surface_id: id,
surface: context,
application_io: self,
})
};
wrapper().expect("should be able to set canvas in global scope")
}
fn destroy_surface(&self, surface_id: SurfaceId) {
self.canvases.borrow_mut().remove(&surface_id);
let window = window().expect("should have a window in this context");
let window = Object::from(window);
let image_canvases_key = JsValue::from_str("imageCanvases");
let wrapper = || {
if let Ok(canvases) = Reflect::get(&window, &image_canvases_key) {
// Convert key and value to JsValue
let js_key = JsValue::from_str(format!("canvas{}", surface_id.0).as_str());
// Use Reflect API to set property
Reflect::delete_property(&canvases.into(), &js_key)?;
}
Ok::<_, JsValue>(())
};
wrapper().expect("should be able to set canvas in global scope")
}
}
pub type WasmSurfaceHandle<'a> = SurfaceHandle<'a, CanvasRenderingContext2d>;
pub type WasmSurfaceHandleFrame<'a> = SurfaceHandleFrame<'a, CanvasRenderingContext2d>;
pub struct CreateSurfaceNode {}
#[node_macro::node_fn(CreateSurfaceNode)]
fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi<'a>) -> Arc<SurfaceHandle<'a, CanvasRenderingContext2d>> {
editor.application_io.create_surface().into()
}
pub struct DrawImageFrameNode<Surface> {
surface_handle: Surface,
}
#[node_macro::node_fn(DrawImageFrameNode)]
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<SurfaceHandle<'a, CanvasRenderingContext2d>>) -> SurfaceHandleFrame<'a, CanvasRenderingContext2d> {
let image_data = image.image.data;
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
if image.image.width > 0 && image.image.height > 0 {
let canvas = surface_handle.surface.canvas().expect("Failed to get canvas");
canvas.set_width(image.image.width);
canvas.set_height(image.image.height);
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width as u32, image.image.height as u32).expect("Failed to construct ImageData");
surface_handle.surface.put_image_data(&image_data, 0.0, 0.0).unwrap();
}
SurfaceHandleFrame {
surface_handle: surface_handle.into(),
transform: image.transform,
}
}

View File

@@ -33,6 +33,8 @@ pub use graphic_element::*;
#[cfg(feature = "alloc")]
pub mod vector;
pub mod application_io;
pub mod quantization;
use core::any::TypeId;
@@ -142,5 +144,6 @@ impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> +
}
}
#[cfg(feature = "alloc")]
pub use crate::raster::image::{EditorApi, ExtractImageFrame};
pub use crate::application_io::{ExtractImageFrame, SurfaceFrame, SurfaceId};
#[cfg(feature = "wasm")]
pub use application_io::{wasm_application_io, wasm_application_io::WasmEditorApi as EditorApi};

View File

@@ -22,7 +22,7 @@ pub struct AddParameterNode<Second> {
second: Second,
}
#[node_macro::node_new(AddParameterNode)]
#[node_macro::node_fn(AddParameterNode)]
fn add_parameter<U, T>(first: U, second: T) -> <U as Add<T>>::Output
where
U: Add<T>,
@@ -30,24 +30,6 @@ where
first + second
}
#[automatically_derived]
impl<'input, U: 'input, T: 'input, S0: 'input> Node<'input, U> for AddParameterNode<S0>
where
U: Add<T>,
S0: Node<'input, (), Output = T>,
{
type Output = <U as Add<T>>::Output;
#[inline]
fn eval(&'input self, first: U) -> Self::Output {
let second = self.second.eval(());
{
{
first + second
}
}
}
}
pub struct MulParameterNode<Second> {
second: Second,
}
@@ -224,6 +206,18 @@ where
}
}
pub struct IntoNode<I, O> {
_i: PhantomData<I>,
_o: PhantomData<O>,
}
#[node_macro::node_fn(IntoNode<_I, _O>)]
fn into<_I, _O>(input: _I) -> _O
where
_I: Into<_O>,
{
input.into()
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -5,7 +5,7 @@ use crate::Node;
use bytemuck::{Pod, Zeroable};
use glam::DVec2;
pub use self::color::{Color, Luma};
pub use self::color::{Color, Luma, SRGBA8};
pub mod adjustments;
pub mod bbox;

View File

@@ -451,8 +451,8 @@ pub struct BlendNode<BlendMode, Opacity> {
}
#[node_macro::node_fn(BlendNode)]
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f32) -> Color {
let opacity = opacity / 100.;
fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Color {
let opacity = opacity as f32 / 100.;
let (foreground, background) = input;

View File

@@ -12,7 +12,92 @@ use spirv_std::num_traits::Euclid;
use bytemuck::{Pod, Zeroable};
use super::{Alpha, AssociatedAlpha, Luminance, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
use super::{
discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float},
Alpha, AssociatedAlpha, Luminance, Pixel, RGBMut, Rec709Primaries, RGB, SRGB,
};
#[repr(C)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, Pod, Zeroable)]
pub struct SRGBA8 {
red: u8,
green: u8,
blue: u8,
alpha: u8,
}
impl From<Color> for SRGBA8 {
#[inline(always)]
fn from(c: Color) -> Self {
Self {
red: float_to_srgb_u8(c.r()),
green: float_to_srgb_u8(c.g()),
blue: float_to_srgb_u8(c.b()),
alpha: (c.a() * 255.0) as u8,
}
}
}
impl From<SRGBA8> for Color {
#[inline(always)]
fn from(color: SRGBA8) -> Self {
Self {
red: srgb_u8_to_float(color.red),
green: srgb_u8_to_float(color.green),
blue: srgb_u8_to_float(color.blue),
alpha: color.alpha as f32 / 255.0,
}
}
}
impl Luminance for SRGBA8 {
type LuminanceChannel = f32;
#[inline(always)]
fn luminance(&self) -> f32 {
// TODO: verify this is correct for sRGB
0.2126 * self.red() + 0.7152 * self.green() + 0.0722 * self.blue()
}
}
impl RGB for SRGBA8 {
type ColorChannel = f32;
#[inline(always)]
fn red(&self) -> f32 {
self.red as f32 / 255.0
}
#[inline(always)]
fn green(&self) -> f32 {
self.green as f32 / 255.0
}
#[inline(always)]
fn blue(&self) -> f32 {
self.blue as f32 / 255.0
}
}
impl Rec709Primaries for SRGBA8 {}
impl SRGB for SRGBA8 {}
impl Alpha for SRGBA8 {
type AlphaChannel = f32;
#[inline(always)]
fn alpha(&self) -> f32 {
self.alpha as f32 / 255.0
}
const TRANSPARENT: Self = SRGBA8 { red: 0, green: 0, blue: 0, alpha: 0 };
fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self {
let alpha = alpha * 255.0;
let mut result = *self;
result.alpha = (alpha * self.alpha()) as u8;
result
}
}
impl Pixel for SRGBA8 {}
#[repr(C)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

View File

@@ -325,44 +325,43 @@ impl<P: Hash + Pixel> Hash for ImageFrame<P> {
}
}
use crate::text::FontCache;
#[derive(Clone, Debug, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EditorApi<'a> {
#[cfg_attr(feature = "serde", serde(skip))]
pub image_frame: Option<ImageFrame<Color>>,
#[cfg_attr(feature = "serde", serde(skip))]
pub font_cache: Option<&'a FontCache>,
}
/* This does not work because of missing specialization
* so we have to manually implement this for now
impl<S: Into<P> + Pixel, P: Pixel> From<Image<S>> for Image<P> {
fn from(image: Image<S>) -> Self {
let data = image.data.into_iter().map(|x| x.into()).collect();
Self {
data,
width: image.width,
height: image.height,
}
}
}*/
unsafe impl StaticType for EditorApi<'_> {
type Static = EditorApi<'static>;
}
impl EditorApi<'_> {
pub fn empty() -> Self {
Self { image_frame: None, font_cache: None }
impl From<ImageFrame<Color>> for ImageFrame<SRGBA8> {
fn from(image: ImageFrame<Color>) -> Self {
let data = image.image.data.into_iter().map(|x| x.into()).collect();
Self {
image: Image {
data,
width: image.image.width,
height: image.image.height,
},
transform: image.transform,
}
}
}
impl<'a> AsRef<EditorApi<'a>> for EditorApi<'a> {
fn as_ref(&self) -> &EditorApi<'a> {
self
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ExtractImageFrame;
impl<'a: 'input, 'input> Node<'input, &'a EditorApi<'a>> for ExtractImageFrame {
type Output = ImageFrame<Color>;
fn eval(&'input self, editor_api: &'a EditorApi<'a>) -> Self::Output {
editor_api.image_frame.clone().unwrap_or(ImageFrame::identity())
}
}
impl ExtractImageFrame {
pub fn new() -> Self {
Self
impl From<ImageFrame<SRGBA8>> for ImageFrame<Color> {
fn from(image: ImageFrame<SRGBA8>) -> Self {
let data = image.image.data.into_iter().map(|x| x.into()).collect();
Self {
image: Image {
data,
width: image.image.width,
height: image.image.height,
},
transform: image.transform,
}
}
}

View File

@@ -16,6 +16,6 @@ pub struct TextGenerator<Text, FontName, Size> {
#[node_fn(TextGenerator)]
fn generate_text<'a: 'input>(editor: &'a EditorApi<'a>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
let buzz_face = editor.font_cache.and_then(|cache| cache.get(&font_name)).map(|data| load_face(data));
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
}

View File

@@ -45,7 +45,7 @@ pub struct DocumentNode {
impl DocumentNode {
pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, lambda: bool) {
let input = self
let (index, _) = self
.inputs
.iter()
.enumerate()
@@ -53,7 +53,6 @@ impl DocumentNode {
.nth(offset)
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
let index = input.0;
self.inputs[index] = NodeInput::Node { node_id, output_index, lambda };
}
@@ -67,7 +66,7 @@ impl DocumentNode {
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
}
NodeInput::Node { node_id, output_index, lambda } => {
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode.");
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode. {:#?}", self.name);
(ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![]))
}
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
@@ -564,9 +563,8 @@ impl NodeNetwork {
self.previous_outputs
.iter_mut()
.for_each(|nodes| nodes.iter_mut().for_each(|output| output.node_id = f(output.node_id)));
let mut empty = HashMap::new();
std::mem::swap(&mut self.nodes, &mut empty);
self.nodes = empty
let nodes = std::mem::take(&mut self.nodes);
self.nodes = nodes
.into_iter()
.map(|(id, mut node)| {
node.inputs.iter_mut().for_each(|input| input.map_ids(f));
@@ -596,70 +594,28 @@ impl NodeNetwork {
network.generate_node_paths(new_path.as_slice());
}
if node.path.is_some() {
log::warn!("Overwriting node path");
log::warn!("Attempting to overwrite node path");
} else {
node.path = Some(new_path);
}
node.path = Some(new_path);
}
}
/// When a node has multiple outputs, we actually just duplicate the node and evaluate each output separately
pub fn duplicate_outputs(&mut self, mut gen_id: &mut impl FnMut() -> NodeId) {
let mut duplicating_nodes = HashMap::new();
// Find the nodes where the inputs require duplicating
for node in &mut self.nodes.values_mut() {
// Recursively duplicate children
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
network.duplicate_outputs(gen_id);
}
for input in &mut node.inputs {
let &mut NodeInput::Node { node_id, output_index, .. } = input else {
continue;
};
// Use the initial node when getting the first output
if output_index == 0 {
continue;
}
// Get the existing duplicated node id (or create a new one)
let duplicated_node_id = *duplicating_nodes.entry((node_id, output_index)).or_insert_with(&mut gen_id);
// Use the first output from the duplicated node
*input = NodeInput::node(duplicated_node_id, 0);
}
}
// Find the network outputs that require duplicating
for network_output in &mut self.outputs {
// Use the initial node when getting the first output
if network_output.node_output_index == 0 {
continue;
}
// Get the existing duplicated node id (or create a new one)
let duplicated_node_id = *duplicating_nodes.entry((network_output.node_id, network_output.node_output_index)).or_insert_with(&mut gen_id);
// Use the first output from the duplicated node
*network_output = NodeOutput::new(duplicated_node_id, 0);
}
// Duplicate the nodes
for ((original_node_id, output_index), new_node_id) in duplicating_nodes {
let Some(original_node) = self.nodes.get(&original_node_id) else {
continue;
};
let mut new_node = original_node.clone();
// Update the required outputs from a nested network to be just the relevant output
if let DocumentNodeImplementation::Network(network) = &mut new_node.implementation {
if network.outputs.is_empty() {
continue;
}
network.outputs = vec![network.outputs[output_index]];
}
self.nodes.insert(new_node_id, new_node);
}
// Ensure all nodes only have one output
fn replace_node_inputs(&mut self, old_input: NodeInput, new_input: NodeInput) {
for node in self.nodes.values_mut() {
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
if network.outputs.is_empty() {
continue;
let node_string = format!("{:?}", node);
node.inputs.iter_mut().for_each(|input| {
if *input == old_input {
*input = new_input.clone();
}
network.outputs = vec![network.outputs[0]];
});
}
}
fn replace_network_outputs(&mut self, old_output: NodeOutput, new_output: NodeOutput) {
for output in self.outputs.iter_mut() {
if *output == old_output {
*output = new_output.clone();
}
}
}
@@ -715,8 +671,14 @@ impl NodeNetwork {
self.nodes.insert(id, node);
return;
}
// replace value inputs with value nodes
for input in &mut node.inputs {
// Skip inputs that are already value nodes
if node.implementation == DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()) {
break;
}
let mut dummy_input = NodeInput::ShortCircut(concrete!(()));
std::mem::swap(&mut dummy_input, input);
if let NodeInput::Value { tagged_value, exposed } = dummy_input {
@@ -749,54 +711,61 @@ impl NodeNetwork {
}
}
match node.implementation {
DocumentNodeImplementation::Network(mut inner_network) => {
// Connect all network inputs to either the parent network nodes, or newly created value nodes.
inner_network.map_ids(|inner_id| map_ids(id, inner_id));
let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>();
// Copy nodes from the inner network into the parent network
self.nodes.extend(inner_network.nodes);
self.disabled.extend(inner_network.disabled);
if let DocumentNodeImplementation::Network(mut inner_network) = node.implementation {
// Connect all network inputs to either the parent network nodes, or newly created value nodes.
inner_network.map_ids(|inner_id| map_ids(id, inner_id));
let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>();
// Copy nodes from the inner network into the parent network
self.nodes.extend(inner_network.nodes);
self.disabled.extend(inner_network.disabled);
let mut network_offsets = HashMap::new();
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
let offset = network_offsets.entry(network_input).or_insert(0);
match document_input {
NodeInput::Node { node_id, output_index, lambda } => {
let network_input = self.nodes.get_mut(network_input).unwrap();
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
}
NodeInput::Network(_) => {
*network_offsets.get_mut(network_input).unwrap() += 1;
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
self.inputs[index] = *network_input;
}
}
NodeInput::ShortCircut(_) => (),
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
NodeInput::Inline(_) => (),
let mut network_offsets = HashMap::new();
assert_eq!(
node.inputs.len(),
inner_network.inputs.len(),
"The number of inputs to the node and the inner network must be the same {}",
node.name
);
// Match the document node input and the inputs of the inner network
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
// Keep track of how many network inputs we have already connected for each node
let offset = network_offsets.entry(network_input).or_insert(0);
match document_input {
// If the input to self is a node, connect the corresponding output of the inner network to it
NodeInput::Node { node_id, output_index, lambda } => {
let network_input = self.nodes.get_mut(network_input).unwrap();
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
}
}
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
node.inputs = inner_network
.outputs
.iter()
.map(|&NodeOutput { node_id, node_output_index }| NodeInput::Node {
node_id,
output_index: node_output_index,
lambda: false,
})
.collect();
for node_id in new_nodes {
self.flatten_with_fns(node_id, map_ids, gen_id);
NodeInput::Network(_) => {
*network_offsets.get_mut(network_input).unwrap() += 1;
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
self.inputs[index] = *network_input;
}
}
NodeInput::ShortCircut(_) => (),
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
NodeInput::Inline(_) => (),
}
}
DocumentNodeImplementation::Unresolved(_) => (),
DocumentNodeImplementation::Extract => (),
// Connect all nodes that were previously connected to this node to the nodes of the inner network
for (i, output) in inner_network.outputs.into_iter().enumerate() {
let node_input = |node_id, output_index, lambda| NodeInput::Node { node_id, output_index, lambda };
self.replace_node_inputs(node_input(id, i, false), node_input(output.node_id, output.node_output_index, false));
self.replace_node_inputs(node_input(id, i, true), node_input(output.node_id, output.node_output_index, true));
self.replace_network_outputs(NodeOutput::new(id, i), output);
}
for node_id in new_nodes {
self.flatten_with_fns(node_id, map_ids, gen_id);
}
} else {
// If the node is not a network, it is a primitive node and can be inserted into the network as is.
assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict");
self.nodes.insert(id, node);
}
assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict");
self.nodes.insert(id, node);
}
fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> {
@@ -1090,17 +1059,8 @@ mod test {
fn resolve_flatten_add_as_proto_network() {
let construction_network = ProtoNetwork {
inputs: vec![10],
output: 1,
output: 11,
nodes: [
(
1,
ProtoNode {
identifier: "graphene_core::ops::IdNode".into(),
input: ProtoNodeInput::Node(11, false),
construction_args: ConstructionArgs::Nodes(vec![]),
document_node_path: vec![1],
},
),
(
10,
ProtoNode {
@@ -1135,18 +1095,8 @@ mod test {
fn flat_network() -> NodeNetwork {
NodeNetwork {
inputs: vec![10],
outputs: vec![NodeOutput::new(1, 0)],
outputs: vec![NodeOutput::new(11, 0)],
nodes: [
(
1,
DocumentNode {
name: "Inc".into(),
inputs: vec![NodeInput::node(11, 0)],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
path: Some(vec![1]),
..Default::default()
},
),
(
10,
DocumentNode {
@@ -1223,7 +1173,7 @@ mod test {
outputs: network_outputs,
nodes: [
(
10,
1,
DocumentNode {
name: "Nested network".into(),
inputs: vec![NodeInput::value(TaggedValue::F32(1.), false), NodeInput::value(TaggedValue::F32(2.), false)],
@@ -1232,7 +1182,7 @@ mod test {
},
),
(
11,
2,
DocumentNode {
name: "Result".into(),
inputs: vec![result_node_input],
@@ -1246,26 +1196,25 @@ mod test {
..Default::default()
};
let mut new_ids = 101..;
network.duplicate_outputs(&mut || new_ids.next().unwrap());
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();
network
}
#[test]
fn simple_duplicate() {
let result = output_duplicate(vec![NodeOutput::new(10, 1)], NodeInput::node(10, 0));
let result = output_duplicate(vec![NodeOutput::new(1, 0)], NodeInput::node(1, 0));
println!("{:#?}", result);
assert_eq!(result.outputs.len(), 1, "The number of outputs should remain as 1");
assert_eq!(result.outputs[0], NodeOutput::new(101, 0), "The outer network output should be from a duplicated inner network");
assert_eq!(result.nodes.keys().copied().collect::<Vec<_>>(), vec![101], "Should just call nested network");
let nested_network_node = result.nodes.get(&101).unwrap();
assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(2.), false)], "Input should be 2");
let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network");
assert_eq!(inner_network.inputs, vec![2], "The input should be sent to the second node");
assert_eq!(inner_network.outputs, vec![NodeOutput::new(2, 0)], "The output should be node id 2");
assert_eq!(inner_network.nodes.get(&2).unwrap().name, "Identity 2", "The node should be identity 2");
assert_eq!(result.outputs[0], NodeOutput::new(11, 0), "The outer network output should be from a duplicated inner network");
let mut ids = result.nodes.keys().copied().collect::<Vec<_>>();
ids.sort();
assert_eq!(ids, vec![11, 10010], "Should only contain identity and values");
}
// TODO: Write more tests
/*
#[test]
fn out_of_order_duplicate() {
let result = output_duplicate(vec![NodeOutput::new(10, 1), NodeOutput::new(10, 0)], NodeInput::node(10, 0));
@@ -1303,4 +1252,5 @@ mod test {
assert_eq!(inner_network.outputs, vec![NodeOutput::new(2, 0)], "The output should be node id 2");
assert_eq!(inner_network.nodes.get(&2).unwrap().name, "Identity 2", "The node should be identity 2");
}
*/
}

View File

@@ -56,11 +56,11 @@ pub enum TaggedValue {
Font(graphene_core::text::Font),
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
Segments(Vec<graphene_core::raster::ImageFrame<Color>>),
EditorApi(graphene_core::EditorApi<'static>),
DocumentNode(DocumentNode),
GraphicGroup(graphene_core::GraphicGroup),
Artboard(graphene_core::Artboard),
IVec2(glam::IVec2),
SurfaceFrame(graphene_core::SurfaceFrame),
}
#[allow(clippy::derived_hash_with_manual_eq)]
@@ -121,11 +121,11 @@ impl Hash for TaggedValue {
segment.hash(state)
}
}
Self::EditorApi(editor_api) => editor_api.hash(state),
Self::DocumentNode(document_node) => document_node.hash(state),
Self::GraphicGroup(graphic_group) => graphic_group.hash(state),
Self::Artboard(artboard) => artboard.hash(state),
Self::IVec2(v) => v.hash(state),
Self::SurfaceFrame(surface_id) => surface_id.hash(state),
}
}
}
@@ -173,11 +173,11 @@ impl<'a> TaggedValue {
TaggedValue::Font(x) => Box::new(x),
TaggedValue::BrushStrokes(x) => Box::new(x),
TaggedValue::Segments(x) => Box::new(x),
TaggedValue::EditorApi(x) => Box::new(x),
TaggedValue::DocumentNode(x) => Box::new(x),
TaggedValue::GraphicGroup(x) => Box::new(x),
TaggedValue::Artboard(x) => Box::new(x),
TaggedValue::IVec2(x) => Box::new(x),
TaggedValue::SurfaceFrame(x) => Box::new(x),
}
}
@@ -237,11 +237,11 @@ impl<'a> TaggedValue {
TaggedValue::Font(_) => concrete!(graphene_core::text::Font),
TaggedValue::BrushStrokes(_) => concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
TaggedValue::Segments(_) => concrete!(graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>),
TaggedValue::EditorApi(_) => concrete!(graphene_core::EditorApi),
TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode),
TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup),
TaggedValue::Artboard(_) => concrete!(graphene_core::Artboard),
TaggedValue::IVec2(_) => concrete!(glam::IVec2),
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
}
}
@@ -289,11 +289,11 @@ impl<'a> TaggedValue {
x if x == TypeId::of::<graphene_core::text::Font>() => Some(TaggedValue::Font(*downcast(input).unwrap())),
x if x == TypeId::of::<Vec<graphene_core::vector::brush_stroke::BrushStroke>>() => Some(TaggedValue::BrushStrokes(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => Some(TaggedValue::Segments(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::EditorApi>() => Some(TaggedValue::EditorApi(*downcast(input).unwrap())),
x if x == TypeId::of::<crate::document::DocumentNode>() => Some(TaggedValue::DocumentNode(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::GraphicGroup>() => Some(TaggedValue::GraphicGroup(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::Artboard>() => Some(TaggedValue::Artboard(*downcast(input).unwrap())),
x if x == TypeId::of::<glam::IVec2>() => Some(TaggedValue::IVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Some(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
_ => None,
}
}

View File

@@ -21,12 +21,10 @@ impl Compiler {
proto_networks.map(move |mut proto_network| {
if resolve_inputs {
println!("resolving inputs");
log::debug!("resolving inputs");
proto_network.resolve_inputs();
}
proto_network.reorder_ids();
proto_network.generate_stable_node_ids();
log::debug!("proto network: {:?}", proto_network);
proto_network
})
}

View File

@@ -204,6 +204,7 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
{
let node_name = self.node.node_name();
let input = Box::new(input);
Box::pin(async move {
let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input).await).unwrap_or_else(|e| panic!("DowncastBothRefNode Input {e}"));

View File

@@ -141,7 +141,8 @@ where
{
type Output = <Input>::Output;
fn eval(&'i self, _: &'i T) -> Self::Output {
self.input.eval(())
let result = self.input.eval(());
result
}
}

View File

@@ -50,7 +50,6 @@ impl DynamicExecutor {
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(), String> {
self.output = proto_network.output;
self.typing_context.update(&proto_network)?;
trace!("setting output to {}", self.output);
let mut orphans = self.tree.update(proto_network, &self.typing_context).await?;
core::mem::swap(&mut self.orphaned_nodes, &mut orphans);
for node_id in orphans {

View File

@@ -8,6 +8,8 @@ use graphene_core::structural::Then;
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::VectorData;
use graphene_core::wasm_application_io::WasmSurfaceHandle;
use graphene_core::wasm_application_io::*;
use graphene_core::{concrete, generic, value_fn};
use graphene_core::{fn_type, raster::*};
use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor};
@@ -22,6 +24,7 @@ use dyn_any::StaticType;
use glam::{DAffine2, DVec2};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
macro_rules! construct_node {
($args: ident, $path:ty, [$($type:tt),*]) => { async move {
@@ -41,7 +44,7 @@ macro_rules! construct_node {
}
macro_rules! register_node {
($path:ty, input: $input:ty, params: [$($type:ty),*]) => {
($path:ty, input: $input:ty, params: [ $($type:ty),*]) => {
vec![
(
NodeIdentifier::new(stringify!($path)),
@@ -158,6 +161,8 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]),
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]),
register_node!(graphene_core::ops::SomeNode, input: graphene_core::EditorApi, params: []),
register_node!(graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ops::IntoNode<_, ImageFrame<Color>>, input: ImageFrame<SRGBA8>, params: []),
register_node!(graphene_std::raster::DownresNode<_>, input: ImageFrame<Color>, params: []),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
@@ -209,6 +214,19 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
register_node!(graphene_std::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
register_node!(graphene_std::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []),
register_node!(graphene_core::wasm_application_io::CreateSurfaceNode, input: &graphene_core::EditorApi, params: []),
vec![(
NodeIdentifier::new("graphene_core::wasm_application_io::DrawImageFrameNode<_>"),
|args| {
Box::pin(async move {
let surface: DowncastBothNode<(), Arc<WasmSurfaceHandle>> = DowncastBothNode::new(args[0]);
let node = graphene_core::wasm_application_io::DrawImageFrameNode::new(surface);
let any: DynAnyNode<ImageFrame<SRGBA8>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
})
},
NodeIOTypes::new(concrete!(ImageFrame<SRGBA8>), concrete!(WasmSurfaceHandleFrame), vec![value_fn!(Arc<WasmSurfaceHandle>)]),
)],
#[cfg(feature = "gpu")]
vec![(
NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"),
@@ -346,7 +364,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
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<(), f32> = DowncastBothNode::new(args[2]);
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
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));
@@ -356,7 +374,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIOTypes::new(
concrete!(ImageFrame<Color>),
concrete!(ImageFrame<Color>),
vec![value_fn!(ImageFrame<Color>), value_fn!(BlendMode), value_fn!(f32)],
vec![value_fn!(ImageFrame<Color>), value_fn!(BlendMode), value_fn!(f64)],
),
)],
raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]),
@@ -475,6 +493,18 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
},
NodeIOTypes::new(generic!(T), concrete!(graphene_core::Artboard), vec![value_fn!(graphene_core::Artboard)]),
),
(
NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"),
|args| {
Box::pin(async move {
let input: DowncastBothNode<(), WasmSurfaceHandleFrame> = DowncastBothNode::new(args[0]);
let node = graphene_std::memo::EndLetNode::new(input);
let any: DynAnyInRefNode<graphene_core::EditorApi, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
Box::pin(any) as TypeErasedPinned
})
},
NodeIOTypes::new(generic!(T), concrete!(WasmSurfaceHandleFrame), vec![value_fn!(WasmSurfaceHandleFrame)]),
),
(
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|args| {
@@ -633,6 +663,18 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
},
NodeIOTypes::new(concrete!(()), concrete!(Vec<DVec2>), vec![value_fn!(Vec<DVec2>)]),
),
(
NodeIdentifier::new("graphene_std::memo::CacheNode"),
|args| {
Box::pin(async move {
let input: DowncastBothNode<(), Arc<WasmSurfaceHandle>> = DowncastBothNode::new(args[0]);
let node: CacheNode<Arc<WasmSurfaceHandle>, _> = graphene_std::memo::CacheNode::new(input);
let any = DynAnyNode::new(ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
})
},
NodeIOTypes::new(concrete!(()), concrete!(Arc<WasmSurfaceHandle>), vec![value_fn!(Arc<WasmSurfaceHandle>)]),
),
],
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
@@ -643,6 +685,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2, DVec2]),
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame<Color>, params: [DVec2, f64, DVec2, DVec2, DVec2]),
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: WasmSurfaceHandleFrame, params: [DVec2, f64, DVec2, DVec2, DVec2]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]),