Implement Infrastructure to reuse previous frames for brush drawing

Implement Infrastructuro to reuse the previous evaluation of the
node graph to blend the new stroke with instead of drawing the
entire trace from scratch.
This does not transition to a blending based approach because that still
caused regressions but allows the brush node to work with input data
natively.

Test Plan:
- Use the brush tool in the editor and check for regressions
- Evaluate the performance

Reviewers: Keavon

Pull Request: https://github.com/GraphiteEditor/Graphite/pull/1190
This commit is contained in:
Dennis Kobert
2023-05-03 13:14:41 +02:00
committed by GitHub
parent 7c115b3b26
commit a3498fe182
19 changed files with 282 additions and 172 deletions
@@ -8,14 +8,18 @@ use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::LayerId;
use dyn_any::downcast_ref;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
use graph_craft::{concrete, Type, TypeDescriptor};
use graphene_core::Cow;
use graphene_core::Color;
use glam::DVec2;
use graphene_core::raster::ImageFrame;
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -172,7 +176,7 @@ impl ToolTransition for BrushTool {
#[derive(Clone, Debug, Default)]
struct BrushToolData {
points: Vec<DVec2>,
points: Vec<Vec<DVec2>>,
diameter: f64,
hardness: f64,
flow: f64,
@@ -181,12 +185,28 @@ struct BrushToolData {
impl BrushToolData {
fn update_points(&self, responses: &mut VecDeque<Message>) {
if let Some(layer_path) = self.path.clone() {
let points = self.points.iter().flatten().cloned().collect();
responses.add(NodeGraphMessage::SetQualifiedInputValue {
layer_path,
node_path: vec![0],
input_index: 2,
value: TaggedValue::VecDVec2(points),
});
}
}
fn update_image(&self, node_graph: &NodeGraphExecutor, responses: &mut VecDeque<Message>) {
let Some(image) = node_graph.introspect_node(&[1]) else { return; };
let image: &ImageFrame<Color> = image.downcast_ref().unwrap();
self.set_image(image.clone(), responses)
}
fn set_image(&self, image_frame: ImageFrame<Color>, responses: &mut VecDeque<Message>) {
if let Some(layer_path) = self.path.clone() {
responses.add(NodeGraphMessage::SetQualifiedInputValue {
layer_path,
node_path: vec![0],
input_index: 1,
value: TaggedValue::VecDVec2(self.points.clone()),
value: TaggedValue::ImageFrame(image_frame),
});
}
}
@@ -201,7 +221,11 @@ impl Fsm for BrushToolFsmState {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, global_tool_data, input, ..
document,
global_tool_data,
input,
node_graph,
..
}: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
@@ -217,9 +241,12 @@ impl Fsm for BrushToolFsmState {
responses.add(DocumentMessage::StartTransaction);
let existing_points = load_existing_points(document);
let new_layer = existing_points.is_none();
if let Some((layer_path, points)) = existing_points {
if let Some((layer_path, points, image)) = existing_points {
tool_data.path = Some(layer_path);
tool_data.points = points;
//tool_data.set_image(image, responses);
if tool_data.points.is_empty() {
tool_data.points.push(points);
}
} else {
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.path = Some(document.get_path_for_new_layer());
@@ -227,7 +254,7 @@ impl Fsm for BrushToolFsmState {
let pos = transform.inverse().transform_point2(input.mouse.position);
tool_data.points.push(pos);
tool_data.points.push(vec![pos]);
tool_data.diameter = tool_options.diameter;
tool_data.hardness = tool_options.hardness;
@@ -236,6 +263,7 @@ impl Fsm for BrushToolFsmState {
if new_layer {
add_brush_render(tool_data, global_tool_data, responses);
} else {
//tool_data.update_image(node_graph, responses);
tool_data.update_points(responses);
}
@@ -244,15 +272,21 @@ impl Fsm for BrushToolFsmState {
(Drawing, PointerMove) => {
let pos = transform.inverse().transform_point2(input.mouse.position);
if tool_data.points.last() != Some(&pos) {
if tool_data.points.last().and_then(|x| x.last()) != Some(&pos) {
// Linear interpolation for when the mouse has moved a lot between frames
if let Some(&last_point) = tool_data.points.last() {
if let Some(&last_point) = tool_data.points.last().and_then(|x| x.last()) {
let distance = (last_point - pos).length();
let extra_points = (distance / (tool_data.diameter / 2.)).floor() as usize;
tool_data.points.extend((0..extra_points).map(|i| last_point.lerp(pos, (i as f64 + 1.) / (extra_points as f64 + 1.))));
tool_data
.points
.last_mut()
.unwrap()
.extend((0..extra_points).map(|i| last_point.lerp(pos, (i as f64 + 1.) / (extra_points as f64 + 1.))));
}
tool_data.points.push(pos);
if let Some(x) = tool_data.points.last_mut() {
x.push(pos)
}
}
tool_data.update_points(responses);
@@ -266,8 +300,8 @@ impl Fsm for BrushToolFsmState {
responses.add(DocumentMessage::AbortTransaction);
}
tool_data.path = None;
tool_data.points.clear();
tool_data.path = None;
Ready
}
@@ -294,11 +328,13 @@ impl Fsm for BrushToolFsmState {
fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
let layer_path = data.path.clone().unwrap();
let brush_node = DocumentNode {
name: "Brush".to_string(),
inputs: vec![
NodeInput::ShortCircut(concrete!(())),
NodeInput::value(TaggedValue::VecDVec2(data.points.clone()), false),
NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
NodeInput::value(TaggedValue::VecDVec2(data.points.last().cloned().unwrap_or_default()), false),
// Diameter
NodeInput::value(TaggedValue::F64(data.diameter), false),
// Hardness
@@ -312,12 +348,18 @@ fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, response
metadata: graph_craft::document::DocumentNodeMetadata { position: (8, 4).into() },
..Default::default()
};
let monitor_node = DocumentNode {
name: "Monitor".to_string(),
implementation: DocumentNodeImplementation::Unresolved("graphene_std::memo::MonitorNode<_>".into()),
..Default::default()
};
let mut network = NodeNetwork::value_network(brush_node);
//network.push_node(monitor_node, true);
network.push_output_node();
graph_modification_utils::new_custom_layer(network, layer_path, responses);
}
fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerId>, Vec<DVec2>)> {
fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerId>, Vec<DVec2>, ImageFrame<Color>)> {
if document.selected_layers().count() != 1 {
return None;
}
@@ -327,11 +369,16 @@ fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerI
if brush_node.implementation != DocumentNodeImplementation::Unresolved("graphene_std::brush::BrushNode".into()) {
return None;
}
let points_input = brush_node.inputs.get(1)?;
let image_input = brush_node.inputs.get(1)?;
let NodeInput::Value {
tagged_value: TaggedValue::ImageFrame(image_frame),
..
} = image_input else { return None };
let points_input = brush_node.inputs.get(2)?;
let NodeInput::Value {
tagged_value: TaggedValue::VecDVec2(points),
..
} = points_input else { return None };
Some((layer_path, points.clone()))
Some((layer_path, points.clone(), image_frame.clone()))
}