mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 21:18:12 +08:00
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:
@@ -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()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user