Graphene: Fine-grained context caching (#2500)

* RFC: Fine Grained Context Caching

* Fix typos

* Fix label

* Add description of inject traits

* Explicitly support context modification

* Start implementation of context invalidation

* Add inject trait variants
* Route Extract / Inject traits to the proto nodes

* Implement context dependency analysis

* Implement context modification node insertion

* Fix erronous force graph run message

* Fix Extract* Inject* annotations in the nodes

* Require Hash implementation for VarArgs

* Fix nullification node insertion

* Cross of done items unresolved questions section

* Update Cargo.lock

* Fix context features propagation

* Update demo artwork

* Remove BondlessFootprint and FreezeRealTime nodes

* Fix migration

* Add migrations for adding context features to old networks

* Always update real time regardless of animation state

* Cargo fmt

* Fix tests

* Readd sed command to hopefully fix profile result parsing

* Add debug output to profiling pr

* Use new totals instead of summaries for for iai results

* Even more debugging

* Use correct debug metrics (hopefully)

* Add more MemoNode implementations

* Add context features annotation to shader node macro

* Cleanup

* Time -> RealTime

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2025-09-05 13:44:26 +02:00
committed by GitHub
parent c081d0a9de
commit acd7ba38cc
39 changed files with 869 additions and 328 deletions

View File

@@ -46,6 +46,10 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Draw))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(
NodeGraphMessageDiscriminant::RunDocumentGraph,
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitActiveGraphRender),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderRulers)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderScrollbars)),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),

View File

@@ -99,14 +99,11 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
}
}
AnimationMessage::UpdateTime => {
if self.is_playing() {
responses.add(PortfolioMessage::SubmitActiveGraphRender);
if self.live_preview_recently_zero {
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.live_preview_recently_zero = false;
}
responses.add(PortfolioMessage::SubmitActiveGraphRender);
if self.is_playing() && self.live_preview_recently_zero {
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.live_preview_recently_zero = false;
}
}
AnimationMessage::RestartAnimation => {

View File

@@ -2604,7 +2604,7 @@ impl DocumentMessageHandler {
layout: Layout::WidgetLayout(document_bar_layout),
layout_target: LayoutTarget::DocumentBar,
});
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
pub fn update_layers_panel_control_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {

View File

@@ -145,74 +145,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "General",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::None, true)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
input_metadata: vec![("Data", "TODO").into()],
output_names: vec!["Data".to_string()],
..Default::default()
@@ -1517,7 +1455,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(3), 0)],
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: vec![
DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
@@ -1531,18 +1469,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -1576,22 +1502,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -1615,7 +1525,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(4), 0)],
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
@@ -1644,18 +1554,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -1702,22 +1600,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -1781,7 +1663,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(3), 0)],
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![
@@ -1799,18 +1681,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -1845,22 +1715,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()

View File

@@ -43,6 +43,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
fields,
description,
properties,
context_features,
} = metadata;
let Some(implementations) = &node_registry.get(id) else { continue };
@@ -59,10 +60,11 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
node_template: NodeTemplate {
document_node: DocumentNode {
inputs,
call_argument: (input_type.clone()),
call_argument: input_type.clone(),
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
visible: true,
skip_deduplication: false,
context_features: ContextDependencies::from(context_features.as_slice()),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {

View File

@@ -16,6 +16,7 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
use graph_craft::{Type, concrete};
use graphene_std::Artboard;
use graphene_std::ContextDependencies;
use graphene_std::math::quad::Quad;
use graphene_std::subpath::Subpath;
use graphene_std::table::Table;
@@ -1248,11 +1249,7 @@ impl NodeNetworkInterface {
/// Returns the display name of the node. If the display name is empty, it will return "Untitled Node" or "Untitled Layer" depending on the node type.
pub fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
let is_layer = self
.node_metadata(node_id, network_path)
.expect("Could not get persistent node metadata in untitled_layer_label")
.persistent_metadata
.is_layer();
let is_layer = self.is_layer(node_id, network_path);
let Some(reference) = self.reference(node_id, network_path) else {
log::error!("Could not get reference in untitled_layer_label");
@@ -4205,6 +4202,18 @@ impl NodeNetworkInterface {
node.call_argument = call_argument;
}
pub fn set_context_features(&mut self, node_id: &NodeId, network_path: &[NodeId], context_features: ContextDependencies) {
let Some(network) = self.network_mut(network_path) else {
log::error!("Could not get nested network in set_context_features");
return;
};
let Some(node) = network.nodes.get_mut(node_id) else {
log::error!("Could not get node in set_context_features");
return;
};
node.context_features = context_features;
}
pub fn set_input(&mut self, input_connector: &InputConnector, new_input: NodeInput, network_path: &[NodeId]) {
if matches!(input_connector, InputConnector::Export(_)) && matches!(new_input, NodeInput::Network { .. }) {
// TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762

View File

@@ -450,14 +450,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::transform_nodes::transform::IDENTIFIER,
aliases: &["graphene_core::transform::TransformNode"],
},
NodeReplacement {
node: graphene_std::transform_nodes::boundless_footprint::IDENTIFIER,
aliases: &["graphene_core::transform::BoundlessFootprintNode"],
},
NodeReplacement {
node: graphene_std::transform_nodes::freeze_real_time::IDENTIFIER,
aliases: &["graphene_core::transform::FreezeRealTimeNode"],
},
// ???
NodeReplacement {
node: graphene_std::vector::spline::IDENTIFIER,
@@ -485,7 +477,13 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
},
NodeReplacement {
node: graphene_std::ops::identity::IDENTIFIER,
aliases: &["graphene_core::transform::CullNode"],
aliases: &[
"graphene_core::transform::CullNode",
"graphene_core::transform::BoundlessFootprintNode",
"graphene_core::transform::FreezeRealTimeNode",
"graphene_core::transform_nodes::BoundlessFootprintNode",
"graphene_core::transform_nodes::FreezeRealTimeNode",
],
},
NodeReplacement {
node: graphene_std::vector::flatten_path::IDENTIFIER,
@@ -560,7 +558,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
let mut default_template = NodeTemplate::default();
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone());
document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
document.network_interface.set_call_argument(node_id, &network_path, graph_craft::Type::Generic("T".into()));
document.network_interface.set_call_argument(node_id, &network_path, default_template.document_node.call_argument);
}
}
}
@@ -587,9 +585,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` as their call argument
if node.call_argument == graph_craft::concrete!(()) || node.call_argument == graph_craft::concrete!(graphene_std::transform::Footprint) {
document
.network_interface
.set_call_argument(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
document.network_interface.set_call_argument(node_id, network_path, graph_craft::concrete!(graphene_std::Context));
}
// Only nodes that have not been modified and still refer to a definition can be updated
@@ -1051,6 +1047,16 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
document.network_interface.add_import(TaggedValue::U32(0), false, 1, "Loop Level", "TODO", &node_path);
}
// Add context features to nodes that don't have them (fine-grained context caching migration)
if node.context_features == graphene_std::ContextDependencies::default() {
if let Some(reference) = document.network_interface.reference(node_id, network_path).cloned().flatten() {
if let Some(node_definition) = resolve_document_node_type(&reference) {
let context_features = node_definition.node_template.document_node.context_features;
document.network_interface.set_context_features(node_id, network_path, context_features);
}
}
}
// ==================================
// PUT ALL MIGRATIONS ABOVE THIS LINE
// ==================================